diff --git a/.gitignore b/.gitignore index 1a6e8fcfe4..491291b901 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .kdev4 *.kdev4 build *.yaml +!tests/data/* *.parse.txt tips diff --git a/src/config/clangtidypreferences.cpp b/src/config/clangtidypreferences.cpp index 4f9ed5f031..8a27374ca7 100644 --- a/src/config/clangtidypreferences.cpp +++ b/src/config/clangtidypreferences.cpp @@ -1,70 +1,71 @@ /************************************************************************************* * Copyright (C) 2016 by Carlos Nihelton * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "clangtidypreferences.h" #include #include "clangtidyconfig.h" #include "configgroup.h" #include "ui_clangtidysettings.h" using KDevelop::IPlugin; using ClangTidy::ConfigGroup; using KDevelop::ConfigPage; ClangtidyPreferences::ClangtidyPreferences(IPlugin* plugin, QWidget* parent) : ConfigPage(plugin, ClangtidySettings::self(), parent) { QVBoxLayout* layout = new QVBoxLayout(this); QWidget* widget = new QWidget(this); ui = new Ui::ClangtidySettings(); ui->setupUi(widget); layout->addWidget(widget); } ClangtidyPreferences::~ClangtidyPreferences() { delete ui; } -ConfigPage::ConfigPageType ClangtidyPreferences::configPageType() const{ +ConfigPage::ConfigPageType ClangtidyPreferences::configPageType() const +{ return ConfigPage::AnalyzerConfigPage; } QString ClangtidyPreferences::name() const { return i18n("clang-tidy"); } QString ClangtidyPreferences::fullName() const { return i18n("Configure clang-tidy settings"); } QIcon ClangtidyPreferences::icon() const { return QIcon::fromTheme(QStringLiteral("dialog-ok")); } void ClangtidyPreferences::apply() { ConfigGroup projConf = KSharedConfig::openConfig()->group("Clangtidy"); projConf.writeEntry(ConfigGroup::ExecutablePath, ui->kcfg_clangtidyPath->text()); } diff --git a/src/config/perprojectconfigpage.cpp b/src/config/perprojectconfigpage.cpp index 447c04f6cb..51c9b3bfae 100644 --- a/src/config/perprojectconfigpage.cpp +++ b/src/config/perprojectconfigpage.cpp @@ -1,175 +1,176 @@ /************************************************************************************* * Copyright (C) 2016 by Carlos Nihelton * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "perprojectconfigpage.h" #include "debug.h" #include "plugin.h" #include "ui_genericconfig.h" #include #include namespace ClangTidy { PerProjectConfigPage::PerProjectConfigPage(KDevelop::IProject* project, QWidget* parent) : ConfigPage(nullptr, nullptr, parent) , m_project(project) { ui = new Ui::GenericConfig(); ui->setupUi(this); m_availableChecksModel = new QStringListModel(); ui->checkListView->setModel(m_availableChecksModel); m_selectedItemModel = new QItemSelectionModel(m_availableChecksModel); ui->checkListView->setSelectionModel(m_selectedItemModel); m_config = m_project->projectConfiguration()->group("Clangtidy"); } PerProjectConfigPage::~PerProjectConfigPage(void) { delete ui; } QIcon PerProjectConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("dialog-ok")); } -KDevelop::ConfigPage::ConfigPageType PerProjectConfigPage::configPageType() const{ +KDevelop::ConfigPage::ConfigPageType PerProjectConfigPage::configPageType() const +{ return ConfigPage::AnalyzerConfigPage; } QString PerProjectConfigPage::name() const { return i18n("clang-tidy"); } void PerProjectConfigPage::setActiveChecksReceptorList(QStringList* list) { m_activeChecksReceptor = list; *m_activeChecksReceptor = m_config.readEntry(ConfigGroup::EnabledChecks).split(','); } void PerProjectConfigPage::setList(QStringList list) { m_underlineAvailChecks = list; m_availableChecksModel->setStringList(m_underlineAvailChecks); for (int i = 0; i < m_availableChecksModel->rowCount(); ++i) { QModelIndex index = m_availableChecksModel->index(i, 0); if (index.isValid()) { if (m_activeChecksReceptor->contains((index.data().toString()))) { m_selectedItemModel->select(index, QItemSelectionModel::Select); } else { m_selectedItemModel->select(index, QItemSelectionModel::Deselect); } } } } void PerProjectConfigPage::apply() { // TODO: discover a way to set the project folders where user header files might exist into this option. // Right now it only works with manual entry. m_config.writeEntry(ConfigGroup::HeaderFilter, ui->headerFilterText->text()); m_config.writeEntry(ConfigGroup::AdditionalParameters, ui->clangtidyParameters->text()); m_config.enableEntry(ConfigGroup::CheckSystemHeaders, ui->sysHeadersCheckBox->isChecked()); m_config.enableEntry(ConfigGroup::UseConfigFile, !ui->overrideConfigFileCheckBox->isChecked()); m_config.enableEntry(ConfigGroup::DumpConfig, ui->dumpCheckBox->isChecked()); for (int i = 0; i < m_availableChecksModel->rowCount(); ++i) { QModelIndex index = m_availableChecksModel->index(i, 0); if (index.isValid()) { bool isSelected = m_selectedItemModel->isSelected(index); auto check = index.data().toString(); if (isSelected) { *m_activeChecksReceptor << check; } else { m_activeChecksReceptor->removeAll(check); } } } m_activeChecksReceptor->removeDuplicates(); m_config.writeEntry(ConfigGroup::EnabledChecks, m_activeChecksReceptor->join(',')); } void PerProjectConfigPage::defaults() { bool wasBlocked = signalsBlocked(); blockSignals(true); // TODO: discover a way to set the project folders where user header files might exist into this option. // Right now it only works with manual entry. m_config.writeEntry(ConfigGroup::ExecutablePath, "/usr/bin/clang-tidy"); m_config.writeEntry(ConfigGroup::HeaderFilter, ""); ui->headerFilterText->setText(""); m_config.writeEntry(ConfigGroup::AdditionalParameters, ""); ui->clangtidyParameters->setText(QString("")); m_config.writeEntry(ConfigGroup::CheckSystemHeaders, ""); ui->sysHeadersCheckBox->setChecked(false); m_config.enableEntry(ConfigGroup::UseConfigFile, false); ui->overrideConfigFileCheckBox->setChecked(true); ui->CheckListGroupBox->setEnabled(true); m_config.enableEntry(ConfigGroup::DumpConfig, true); ui->dumpCheckBox->setChecked(true); ui->CheckListGroupBox->setEnabled(true); m_config.enableEntry(ConfigGroup::ExportFixes, true); // ui->autoFixCheckBox->setChecked(true); for (int i = 0; i < m_availableChecksModel->rowCount(); ++i) { QModelIndex index = m_availableChecksModel->index(i, 0); if (index.isValid()) { auto check = index.data().toString(); bool enable = check.contains("cert") || check.contains("-core.") || check.contains("-cplusplus") || check.contains("-deadcode") || check.contains("-security") || check.contains("cppcoreguide"); m_selectedItemModel->select(index, enable ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); if (enable) { *m_activeChecksReceptor << check; } else { m_activeChecksReceptor->removeAll(check); } } } m_activeChecksReceptor->removeDuplicates(); m_config.writeEntry(ConfigGroup::EnabledChecks, m_activeChecksReceptor->join(',')); blockSignals(wasBlocked); } void PerProjectConfigPage::reset() { if (!m_config.isValid()) { return; } ui->headerFilterText->setText(m_config.readEntry(ConfigGroup::HeaderFilter).remove("--header-filter=")); ui->clangtidyParameters->setText(m_config.readEntry(ConfigGroup::AdditionalParameters)); ui->sysHeadersCheckBox->setChecked(!m_config.readEntry(ConfigGroup::CheckSystemHeaders).isEmpty()); ui->overrideConfigFileCheckBox->setChecked(m_config.readEntry(ConfigGroup::UseConfigFile).isEmpty()); ui->CheckListGroupBox->setEnabled(m_config.readEntry(ConfigGroup::UseConfigFile).isEmpty()); ui->dumpCheckBox->setChecked(!m_config.readEntry(ConfigGroup::DumpConfig).isEmpty()); } } // namespace ClangTidy diff --git a/src/parsers/replacementparser.cpp b/src/parsers/replacementparser.cpp index c0b9a085ab..81879d0edf 100644 --- a/src/parsers/replacementparser.cpp +++ b/src/parsers/replacementparser.cpp @@ -1,122 +1,174 @@ -/************************************************************************************** - * Copyright (C) 2016 by Carlos Nihelton * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * - * as published by the Free Software Foundation; either version 2 * - * of the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the Free Software * - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * - ***************************************************************************************/ +/************************************************************************************* + * Copyright (C) 2016 by Carlos Nihelton * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ #include "replacementparser.h" +// See +#include +#include +#include + +#ifdef BOOST_NO_EXCEPTIONS +// Because we are using boost we need to provide an implementation of this function, because KDE disables exceptions on +// boost libraries. +namespace boost +{ +void throw_exception(std::exception const& /* unused */) +{ +} +} +#endif namespace ClangTidy { const QRegularExpression ReplacementParser::check{ QStringLiteral( "---\\s+MainSourceFile:\\s+.+\\s+Replacements:(\\s+.+)+\\s\\.\\.\\.") }; const QRegularExpression ReplacementParser::regex{ ( QStringLiteral("\\s+\\s+-\\s+FilePath:\\s+(.+\\.cpp)\\s+Offset:\\s+(\\d+)\\s+Length:\\s+" "(\\d+)\\s+ ReplacementText:\\s(.+)")) }; ReplacementParser::ReplacementParser(const QString& yaml_file, const QString& source_file) : currentLine{ 0 } , currentColumn{ 0 } , currentOffset{ 0 } , cReplacements{ 0 } , m_yamlname{ yaml_file } , m_sourceFile{ source_file } { if (m_yamlname.endsWith(".yaml")) { QFile yaml(m_yamlname); yaml.open(QIODevice::ReadOnly); m_yamlContent = yaml.readAll(); auto checkMatch = check.match(m_yamlContent); if (!checkMatch.hasMatch()) { m_yamlname.clear(); m_yamlContent.clear(); } } // TODO: Discover a way to get that from KDevelop. if (m_sourceFile.endsWith(".cpp")) { i_source = IndexedString(m_sourceFile); - QFile cpp(m_sourceFile); - cpp.open(QIODevice::ReadOnly); - m_sourceCode = cpp.readAll(); + // See + std::ifstream cpp; + cpp.open(m_sourceFile.toUtf8()); + std::copy(std::istreambuf_iterator(cpp), std::istreambuf_iterator(), + std::back_insert_iterator(m_sourceCode)); + m_sourceView = boost::string_ref(m_sourceCode); + qDebug() << "m_sourceView.length(): " << m_sourceView.length() << '\n'; + qDebug() << "m_sourceCode.length(): " << m_sourceCode.length() << '\n'; } } void ReplacementParser::parse() { if (m_yamlContent.isEmpty()) return; // Nothing to parse. for (auto iMatch = regex.globalMatch(m_yamlContent); iMatch.hasNext(); ++cReplacements) { auto smatch = iMatch.next(); auto rep = nextNode(smatch); all_replacements.push_back(rep); } } Replacement ReplacementParser::nextNode(const QRegularExpressionMatch& smatch) { Replacement repl; if (smatch.captured(1) != m_sourceFile) return repl; // Parsing output from only one file. repl.offset = smatch.captured(2).toInt(); - repl.lenght = smatch.captured(3).toInt(); + repl.length = smatch.captured(3).toInt(); repl.replacementText = smatch.captured(4); if (repl.replacementText.startsWith('\'') && repl.replacementText.endsWith('\'')) { repl.replacementText.remove(0, 1); repl.replacementText.remove(repl.replacementText.length() - 1, 1); } - repl.range = composeNextNodeRange(repl.offset); + repl.range = composeNextNodeRange(repl.offset, repl.length); return repl; } -KDevelop::DocumentRange ReplacementParser::composeNextNodeRange(size_t offset) +KDevelop::DocumentRange ReplacementParser::composeNextNodeRange(size_t offset, size_t length) { KDevelop::DocumentRange range; if (offset < 1) return range; range.document = i_source; - QStringRef sourceView(&m_sourceCode, currentOffset, offset - currentOffset); + auto sourceView = m_sourceView.substr(currentOffset, offset - currentOffset); + qDebug() << "sourceView.length(): " << sourceView.length() << '\n'; size_t line = 0, col = 0; for (const auto elem : sourceView) { - if (elem == QChar('\n')) { + if (elem == char('\n')) { ++line; col = 0; } else { ++col; } } if (line == 0) { currentColumn += col; } else { currentColumn = col; } currentLine += line; currentOffset = offset; - range.setBothColumns(currentColumn); - range.setBothLines(currentLine); + + if (length == 0) { + range.setBothColumns(currentColumn); + range.setBothLines(currentLine); + return range; + } + + sourceView = m_sourceView.substr(offset, length); + qDebug() << "sourceView.length(): " << sourceView.length() << '\n'; + line = 0; + col = 0; + for (const auto elem : sourceView) { + if (elem == char('\n')) { + ++line; + col = 0; + } else { + ++col; + } + } + + KTextEditor::Cursor start(currentLine, currentColumn); + + size_t endCol; + + if (line == 0) { + endCol = currentColumn + col; + } else { + endCol = col; + } + + KTextEditor::Cursor end(currentLine + line, endCol); + + range.setRange(start, end); return range; } -} //namespace ClangTidy +} // namespace ClangTidy diff --git a/src/parsers/replacementparser.h b/src/parsers/replacementparser.h index a6dc42b1a1..78ce9f2624 100644 --- a/src/parsers/replacementparser.h +++ b/src/parsers/replacementparser.h @@ -1,70 +1,72 @@ /************************************************************************************** * Copyright (C) 2016 by Carlos Nihelton * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************************/ #ifndef CLANGTIDY_REPLACEMENT_H #define CLANGTIDY_REPLACEMENT_H #include #include #include +#include #include using KDevelop::DocumentRange; using KDevelop::IndexedString; namespace ClangTidy { struct Replacement { - size_t offset, lenght; // read from YAML. + size_t offset, length; // read from YAML. size_t line, column; // calculated by parser. Might drop eventually. QString replacementText; // read from YAML. DocumentRange range; // created from line and colum. }; using Replacements = QVector; class ReplacementParser { private: size_t currentLine; size_t currentColumn; size_t currentOffset; size_t cReplacements; QString m_yamlname; QString m_sourceFile; IndexedString i_source; QString m_yamlContent; - QString m_sourceCode; + std::string m_sourceCode; + boost::string_ref m_sourceView; static const QRegularExpression regex, check; Replacements all_replacements; protected: Replacement nextNode(const QRegularExpressionMatch& smatch); - KDevelop::DocumentRange composeNextNodeRange(size_t offset); + KDevelop::DocumentRange composeNextNodeRange(size_t offset, size_t length); public: ReplacementParser() = default; explicit ReplacementParser(const QString& yaml_file, const QString& source_file); void parse(); Replacements allReplacements() { return all_replacements; } }; } #endif // CLANGTIDY_REPLACEMENT_H diff --git a/src/plugin.cpp b/src/plugin.cpp index 5031607a47..b21eb07ec9 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -1,290 +1,289 @@ /************************************************************************************* * Copyright (C) 2016 by Carlos Nihelton * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "./config/clangtidypreferences.h" #include "./config/perprojectconfigpage.h" #include "debug.h" #include "job.h" #include "plugin.h" using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(ClangtidyFactory, "res/kdevclangtidy.json", registerPlugin();) namespace ClangTidy { Plugin::Plugin(QObject* parent, const QVariantList& /*unused*/) : IPlugin("kdevclangtidy", parent) , m_model(new KDevelop::ProblemModel(parent)) { qCDebug(KDEV_CLANGTIDY) << "setting clangtidy rc file"; setXMLFile("kdevclangtidy.rc"); QAction* act_checkfile; act_checkfile = actionCollection()->addAction("clangtidy_file", this, SLOT(runClangtidyFile())); act_checkfile->setStatusTip(i18n("Launches Clangtidy for current file")); act_checkfile->setText(i18n("clang-tidy")); /* TODO: Uncomment this only when discover a safe way to run clang-tidy on the whole project. // QAction* act_check_all_files; // act_check_all_files = actionCollection()->addAction ( "clangtidy_all", this, SLOT ( runClangtidyAll() ) ); // act_check_all_files->setStatusTip ( i18n ( "Launches clangtidy for all translation " // "units of current project" ) ); // act_check_all_files->setText ( i18n ( "clang-tidy (all)" ) ); */ IExecutePlugin* iface = KDevelop::ICore::self() ->pluginController() ->pluginForExtension("org.kdevelop.IExecutePlugin") ->extension(); Q_ASSERT(iface); ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("Clangtidy"), m_model.data()); m_config = KSharedConfig::openConfig()->group("Clangtidy"); auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath); // TODO(cnihelton): auto detect clang-tidy executable instead of hard-coding it. if (clangtidyPath.isEmpty()) { clangtidyPath = QString("/usr/bin/clang-tidy"); } collectAllAvailableChecks(clangtidyPath); m_config.writeEntry(ConfigGroup::AdditionalParameters, ""); for (auto check : m_allChecks) { bool enable = check.contains("cert") || check.contains("-core.") || check.contains("-cplusplus") || check.contains("-deadcode") || check.contains("-security") || check.contains("cppcoreguide"); if (enable) { m_activeChecks << check; } else { m_activeChecks.removeAll(check); } } m_activeChecks.removeDuplicates(); m_config.writeEntry(ConfigGroup::EnabledChecks, m_activeChecks.join(',')); } void Plugin::unload() { ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->removeModel(QStringLiteral("Clangtidy")); } void Plugin::collectAllAvailableChecks(QString clangtidyPath) { m_allChecks.clear(); KProcess tidy; tidy << clangtidyPath << QLatin1String("-checks=*") << QLatin1String("--list-checks"); tidy.setOutputChannelMode(KProcess::OnlyStdoutChannel); tidy.start(); if (!tidy.waitForStarted()) { qCDebug(KDEV_CLANGTIDY) << "Unable to execute clang-tidy."; return; } tidy.closeWriteChannel(); if (!tidy.waitForFinished()) { qCDebug(KDEV_CLANGTIDY) << "Failed during clang-tidy execution."; return; } QTextStream ios(&tidy); QString each; while (ios.readLineInto(&each)) { m_allChecks.append(each.trimmed()); } if (m_allChecks.size() > 3) { m_allChecks.removeAt(m_allChecks.length() - 1); m_allChecks.removeAt(0); } m_allChecks.removeDuplicates(); } void Plugin::runClangtidy(bool allFiles) { KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if (!doc) { QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("No suitable active file, unable to deduce project.")); return; } KDevelop::IProject* project = core()->projectController()->findProjectForUrl(doc->url()); if (!project) { QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("Active file isn't in a project")); return; } m_config = project->projectConfiguration()->group("Clangtidy"); if (!m_config.isValid()) { QMessageBox::critical(nullptr, i18n("Error starting Clangtidy"), i18n("Can't load parameters. They must be set in the project settings.")); return; } auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath); auto buildSystem = project->buildSystemManager(); Job::Parameters params; params.projectRootDir = project->path().toLocalFile(); // TODO: auto detect clang-tidy executable instead of hard-coding it. if (clangtidyPath.isEmpty()) { params.executablePath = QStringLiteral("/usr/bin/clang-tidy"); } else { params.executablePath = clangtidyPath; } if (allFiles) { params.filePath = project->path().toUrl().toLocalFile(); } else { params.filePath = doc->url().toLocalFile(); } params.buildDir = buildSystem->buildDirectory(project->projectItem()).toLocalFile(); params.additionalParameters = m_config.readEntry(ConfigGroup::AdditionalParameters); params.analiseTempDtors = m_config.readEntry(ConfigGroup::AnaliseTempDtors); params.enabledChecks = m_activeChecks.join(','); params.useConfigFile = m_config.readEntry(ConfigGroup::UseConfigFile); params.dumpConfig = m_config.readEntry(ConfigGroup::DumpConfig); params.enableChecksProfile = m_config.readEntry(ConfigGroup::EnableChecksProfile); params.exportFixes = m_config.readEntry(ConfigGroup::ExportFixes); params.extraArgs = m_config.readEntry(ConfigGroup::ExtraArgs); params.extraArgsBefore = m_config.readEntry(ConfigGroup::ExtraArgsBefore); params.autoFix = m_config.readEntry(ConfigGroup::AutoFix); params.headerFilter = m_config.readEntry(ConfigGroup::HeaderFilter); params.lineFilter = m_config.readEntry(ConfigGroup::LineFilter); params.listChecks = m_config.readEntry(ConfigGroup::ListChecks); params.checkSystemHeaders = m_config.readEntry(ConfigGroup::CheckSystemHeaders); if (!params.dumpConfig.isEmpty()) { Job* job = new ClangTidy::Job(params, this); core()->runController()->registerJob(job); params.dumpConfig = QString(); } Job* job2 = new ClangTidy::Job(params, this); connect(job2, SIGNAL(finished(KJob*)), this, SLOT(result(KJob*))); core()->runController()->registerJob(job2); } void Plugin::runClangtidyFile() { bool allFiles = false; runClangtidy(allFiles); } void Plugin::runClangtidyAll() { bool allFiles = true; runClangtidy(allFiles); } void Plugin::loadOutput() { } void Plugin::result(KJob* job) { Job* aj = dynamic_cast(job); if (!aj) { return; } if (aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded) { m_model->setProblems(aj->problems()); core()->uiController()->findToolView(i18nd("kdevproblemreporter", "Problems"), 0, KDevelop::IUiController::FindFlags::Raise); } } KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::IDocument* doc = core()->documentController()->activeDocument(); KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if (context->type() == KDevelop::Context::EditorContext) { auto mime = doc->mimeType().name(); if (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc")) { - QAction* action - = new QAction(QIcon::fromTheme("dialog-ok"), i18n("Check unit with clang-tidy"), this); + QAction* action = new QAction(QIcon::fromTheme("dialog-ok"), i18n("Check unit with clang-tidy"), this); connect(action, SIGNAL(triggered(bool)), this, SLOT(runClangtidyFile())); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } return extension; } KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number != 0) { return nullptr; } else { auto config = new PerProjectConfigPage(options.project, parent); config->setActiveChecksReceptorList(&m_activeChecks); config->setList(m_allChecks); return config; } } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { if (number != 0) { return nullptr; } else { return new ClangtidyPreferences(this, parent); } } } #include "plugin.moc" diff --git a/src/plugin.cpp b/tests/data/nihon_plugin.cpp similarity index 73% copy from src/plugin.cpp copy to tests/data/nihon_plugin.cpp index 5031607a47..991f2e89c0 100644 --- a/src/plugin.cpp +++ b/tests/data/nihon_plugin.cpp @@ -1,290 +1,423 @@ /************************************************************************************* * Copyright (C) 2016 by Carlos Nihelton * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + * * + * Now let's read a beautiful song: * + + 夢のつづき + 追いかけていたはずなのに + + 曲がりくねった + 細い道 人につまずく + + あの頃みたいにって + 戻りたい訳じゃないの + + 無くしてきた空を + 探してる + + わかってくれますように + + 犠牲になったような + 悲しい顔はやめてよ + + 罪の最後は涙じゃないよ + ずっと苦しく背負ってくんだ + + 出口見えない感情迷路に + 誰を待ってるの? + + 白いノートに綴ったように + もっと素直に吐き出したいよ + + 何から + 逃れたいんだ + + …現実ってやつ? + + 叶えるために + 生きてるんだって + + 忘れちゃいそうな + 夜の真ん中 + + 無難になんて + やってられないから + + …帰る場所もないの + + この想いを 消してしまうには + まだ人生長いでしょ?(I'm on the way) + + 懐かしくなる + こんな痛みも歓迎じゃん + + 謝らなくちゃいけないよね + ah ごめんね + + うまく言えなくて + 心配かけたままだったね + + あの日かかえた全部 + あしたかかえる全部 + + 順番つけたりは + しないから + + わかってくれますように + + そっと目を閉じたんだ + 見たくないものまで + 見えんだもん + + いらないウワサにちょっと + 初めて聞く発言どっち? + + 2回会ったら友達だって?? + ウソはやめてね + + 赤いハートが苛立つように + 身体ん中燃えているんだ + + ホントは + 期待してんの + + …現実ってやつ? + + 叶えるために + 生きてるんだって + + 叫びたくなるよ + 聞こえていますか? + + 無難になんて + やってられないから + + …帰る場所もないの + + 優しさには いつも感謝してる + だから強くなりたい(I'm on the way) + + 進むために + 敵も味方も歓迎じゃん + + どうやって次のドア + 開けるんだっけ?考えてる? + + もう引き返せない + 物語 始まってるんだ + + 目を覚ませ 目を覚ませ + + この想いを 消してしまうには + まだ人生長いでしょ? + + やり残してるコト + やり直してみたいから + + もう一度ゆこう + + 叶えるために + 生きてるんだって + + 叫びたくなるよ + 聞こえていますか? + + 無難になんて + やってられないから + + …帰る場所もないの + + 優しさには いつも感謝してる + だから強くなりたい(I'm on the way) + + 懐かしくなる + こんな痛みも歓迎じゃん + *************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "./config/clangtidypreferences.h" #include "./config/perprojectconfigpage.h" #include "debug.h" #include "job.h" #include "plugin.h" using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(ClangtidyFactory, "res/kdevclangtidy.json", registerPlugin();) namespace ClangTidy { Plugin::Plugin(QObject* parent, const QVariantList& /*unused*/) : IPlugin("kdevclangtidy", parent) , m_model(new KDevelop::ProblemModel(parent)) { qCDebug(KDEV_CLANGTIDY) << "setting clangtidy rc file"; setXMLFile("kdevclangtidy.rc"); QAction* act_checkfile; act_checkfile = actionCollection()->addAction("clangtidy_file", this, SLOT(runClangtidyFile())); act_checkfile->setStatusTip(i18n("Launches Clangtidy for current file")); act_checkfile->setText(i18n("clang-tidy")); /* TODO: Uncomment this only when discover a safe way to run clang-tidy on the whole project. // QAction* act_check_all_files; // act_check_all_files = actionCollection()->addAction ( "clangtidy_all", this, SLOT ( runClangtidyAll() ) ); // act_check_all_files->setStatusTip ( i18n ( "Launches clangtidy for all translation " // "units of current project" ) ); // act_check_all_files->setText ( i18n ( "clang-tidy (all)" ) ); */ IExecutePlugin* iface = KDevelop::ICore::self() ->pluginController() ->pluginForExtension("org.kdevelop.IExecutePlugin") ->extension(); Q_ASSERT(iface); ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("Clangtidy"), m_model.data()); m_config = KSharedConfig::openConfig()->group("Clangtidy"); auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath); // TODO(cnihelton): auto detect clang-tidy executable instead of hard-coding it. if (clangtidyPath.isEmpty()) { clangtidyPath = QString("/usr/bin/clang-tidy"); } collectAllAvailableChecks(clangtidyPath); m_config.writeEntry(ConfigGroup::AdditionalParameters, ""); for (auto check : m_allChecks) { bool enable = check.contains("cert") || check.contains("-core.") || check.contains("-cplusplus") || check.contains("-deadcode") || check.contains("-security") || check.contains("cppcoreguide"); if (enable) { m_activeChecks << check; } else { m_activeChecks.removeAll(check); } } m_activeChecks.removeDuplicates(); m_config.writeEntry(ConfigGroup::EnabledChecks, m_activeChecks.join(',')); } void Plugin::unload() { ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->removeModel(QStringLiteral("Clangtidy")); } void Plugin::collectAllAvailableChecks(QString clangtidyPath) { m_allChecks.clear(); KProcess tidy; tidy << clangtidyPath << QLatin1String("-checks=*") << QLatin1String("--list-checks"); tidy.setOutputChannelMode(KProcess::OnlyStdoutChannel); tidy.start(); if (!tidy.waitForStarted()) { qCDebug(KDEV_CLANGTIDY) << "Unable to execute clang-tidy."; return; } tidy.closeWriteChannel(); if (!tidy.waitForFinished()) { qCDebug(KDEV_CLANGTIDY) << "Failed during clang-tidy execution."; return; } QTextStream ios(&tidy); QString each; while (ios.readLineInto(&each)) { m_allChecks.append(each.trimmed()); } if (m_allChecks.size() > 3) { m_allChecks.removeAt(m_allChecks.length() - 1); m_allChecks.removeAt(0); } m_allChecks.removeDuplicates(); } void Plugin::runClangtidy(bool allFiles) { KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if (!doc) { QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("No suitable active file, unable to deduce project.")); return; } KDevelop::IProject* project = core()->projectController()->findProjectForUrl(doc->url()); if (!project) { QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("Active file isn't in a project")); return; } m_config = project->projectConfiguration()->group("Clangtidy"); if (!m_config.isValid()) { QMessageBox::critical(nullptr, i18n("Error starting Clangtidy"), i18n("Can't load parameters. They must be set in the project settings.")); return; } auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath); auto buildSystem = project->buildSystemManager(); Job::Parameters params; params.projectRootDir = project->path().toLocalFile(); // TODO: auto detect clang-tidy executable instead of hard-coding it. if (clangtidyPath.isEmpty()) { params.executablePath = QStringLiteral("/usr/bin/clang-tidy"); } else { params.executablePath = clangtidyPath; } if (allFiles) { params.filePath = project->path().toUrl().toLocalFile(); } else { params.filePath = doc->url().toLocalFile(); } params.buildDir = buildSystem->buildDirectory(project->projectItem()).toLocalFile(); params.additionalParameters = m_config.readEntry(ConfigGroup::AdditionalParameters); params.analiseTempDtors = m_config.readEntry(ConfigGroup::AnaliseTempDtors); params.enabledChecks = m_activeChecks.join(','); params.useConfigFile = m_config.readEntry(ConfigGroup::UseConfigFile); params.dumpConfig = m_config.readEntry(ConfigGroup::DumpConfig); params.enableChecksProfile = m_config.readEntry(ConfigGroup::EnableChecksProfile); params.exportFixes = m_config.readEntry(ConfigGroup::ExportFixes); params.extraArgs = m_config.readEntry(ConfigGroup::ExtraArgs); params.extraArgsBefore = m_config.readEntry(ConfigGroup::ExtraArgsBefore); params.autoFix = m_config.readEntry(ConfigGroup::AutoFix); params.headerFilter = m_config.readEntry(ConfigGroup::HeaderFilter); params.lineFilter = m_config.readEntry(ConfigGroup::LineFilter); params.listChecks = m_config.readEntry(ConfigGroup::ListChecks); params.checkSystemHeaders = m_config.readEntry(ConfigGroup::CheckSystemHeaders); if (!params.dumpConfig.isEmpty()) { Job* job = new ClangTidy::Job(params, this); core()->runController()->registerJob(job); params.dumpConfig = QString(); } Job* job2 = new ClangTidy::Job(params, this); connect(job2, SIGNAL(finished(KJob*)), this, SLOT(result(KJob*))); core()->runController()->registerJob(job2); } void Plugin::runClangtidyFile() { bool allFiles = false; runClangtidy(allFiles); } void Plugin::runClangtidyAll() { bool allFiles = true; runClangtidy(allFiles); } void Plugin::loadOutput() { } void Plugin::result(KJob* job) { Job* aj = dynamic_cast(job); if (!aj) { return; } if (aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded) { m_model->setProblems(aj->problems()); core()->uiController()->findToolView(i18nd("kdevproblemreporter", "Problems"), 0, KDevelop::IUiController::FindFlags::Raise); } } KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::IDocument* doc = core()->documentController()->activeDocument(); KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if (context->type() == KDevelop::Context::EditorContext) { auto mime = doc->mimeType().name(); if (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc")) { QAction* action - = new QAction(QIcon::fromTheme("dialog-ok"), i18n("Check unit with clang-tidy"), this); + = new QAction(QIcon::fromTheme("document-new"), i18n("Check current unit with clang-tidy"), this); connect(action, SIGNAL(triggered(bool)), this, SLOT(runClangtidyFile())); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } return extension; } KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number != 0) { return nullptr; } else { auto config = new PerProjectConfigPage(options.project, parent); config->setActiveChecksReceptorList(&m_activeChecks); config->setList(m_allChecks); return config; } } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { if (number != 0) { return nullptr; } else { return new ClangtidyPreferences(this, parent); } } } #include "plugin.moc" diff --git a/tests/data/nihon_plugin.cpp.yaml b/tests/data/nihon_plugin.cpp.yaml new file mode 100644 index 0000000000..fd612b27c1 --- /dev/null +++ b/tests/data/nihon_plugin.cpp.yaml @@ -0,0 +1,72 @@ +--- +MainSourceFile: '' +Replacements: + - FilePath: data/nihon_plugin.cpp + Offset: 10165 + Length: 1 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 10169 + Length: 0 + ReplacementText: ' == nullptr' + - FilePath: data/nihon_plugin.cpp + Offset: 10463 + Length: 1 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 10471 + Length: 0 + ReplacementText: ' == nullptr' + - FilePath: data/nihon_plugin.cpp + Offset: 11135 + Length: 69 + ReplacementText: '// TODO(cnihelton): auto detect clang-tidy executable instead of hard-coding it.' + - FilePath: data/nihon_plugin.cpp + Offset: 12702 + Length: 4 + ReplacementText: 'auto ' + - FilePath: data/nihon_plugin.cpp + Offset: 12847 + Length: 4 + ReplacementText: 'auto ' + - FilePath: data/nihon_plugin.cpp + Offset: 13308 + Length: 1 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 13311 + Length: 0 + ReplacementText: ' == nullptr' + - FilePath: data/nihon_plugin.cpp + Offset: 13551 + Length: 1 + ReplacementText: nullptr + - FilePath: data/nihon_plugin.cpp + Offset: 14652 + Length: 5 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 14657 + Length: 1 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 14859 + Length: 1 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 14989 + Length: 5 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 14994 + Length: 1 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 15055 + Length: 1 + ReplacementText: '' + - FilePath: data/nihon_plugin.cpp + Offset: 15060 + Length: 0 + ReplacementText: ' // namespace ClangTidy' +... diff --git a/tests/data/plugin.cpp.yaml b/tests/data/plugin.cpp.yaml new file mode 100644 index 0000000000..c0487c6540 --- /dev/null +++ b/tests/data/plugin.cpp.yaml @@ -0,0 +1,72 @@ +--- +MainSourceFile: '' +Replacements: + - FilePath: data/plugin.cpp + Offset: 6263 + Length: 1 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 6267 + Length: 0 + ReplacementText: ' == nullptr' + - FilePath: data/plugin.cpp + Offset: 6561 + Length: 1 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 6569 + Length: 0 + ReplacementText: ' == nullptr' + - FilePath: data/plugin.cpp + Offset: 7233 + Length: 69 + ReplacementText: '// TODO(cnihelton): auto detect clang-tidy executable instead of hard-coding it.' + - FilePath: data/plugin.cpp + Offset: 8800 + Length: 4 + ReplacementText: 'auto ' + - FilePath: data/plugin.cpp + Offset: 8945 + Length: 4 + ReplacementText: 'auto ' + - FilePath: data/plugin.cpp + Offset: 9406 + Length: 1 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 9409 + Length: 0 + ReplacementText: ' == nullptr' + - FilePath: data/plugin.cpp + Offset: 9649 + Length: 1 + ReplacementText: nullptr + - FilePath: data/plugin.cpp + Offset: 10750 + Length: 5 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 10755 + Length: 1 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 10957 + Length: 1 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 11087 + Length: 5 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 11092 + Length: 1 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 11153 + Length: 1 + ReplacementText: '' + - FilePath: data/plugin.cpp + Offset: 11158 + Length: 0 + ReplacementText: ' // namespace ClangTidy' +... diff --git a/tests/test_replacementparser.cpp b/tests/test_replacementparser.cpp index 338b1836a9..b0c5bc1274 100644 --- a/tests/test_replacementparser.cpp +++ b/tests/test_replacementparser.cpp @@ -1,111 +1,152 @@ -/* - * ************************************************************************************ - * Copyright (C) 2016 by Carlos Nihelton * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * - * as published by the Free Software Foundation; either version 2 * - * of the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the Free Software * - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * - * ************************************************************************************ - */ +/************************************************************************************* + * Copyright (C) 2016 by Carlos Nihelton * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ #include "test_replacementparser.h" #include #include #include #include "parsers/replacementparser.h" using namespace KDevelop; using namespace ClangTidy; void ReplacementsParserTester::initTestCase() { // Called before the first testfunction is executed AutoTestShell::init({ "kdevclangtidy" }); TestCore::initialize(Core::NoUi); } void ReplacementsParserTester::cleanupTestCase() { // Called after the last testfunction was executed TestCore::shutdown(); } void ReplacementsParserTester::doTest() { ReplacementParser parser("data/plugin.cpp.yaml", "data/plugin.cpp"); parser.parse(); auto v = parser.allReplacements(); QVERIFY(!v.isEmpty()); QCOMPARE(v.length(), 17); QCOMPARE(v[0].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[0].offset, size_t(6263)); - QCOMPARE(v[0].lenght, size_t(1)); + QCOMPARE(v[0].length, size_t(1)); QCOMPARE(v[0].replacementText, QString()); QCOMPARE(v[0].range.start().line() + 1, 155); // as would appear in editor. QCOMPARE(v[0].range.start().column() + 1, 9); // as would appear in editor. QCOMPARE(v[1].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[1].offset, size_t(6267)); - QCOMPARE(v[1].lenght, size_t(0)); + QCOMPARE(v[1].length, size_t(0)); QCOMPARE(v[1].replacementText, QString(" == nullptr")); QCOMPARE(v[1].range.start().line() + 1, 155); QCOMPARE(v[1].range.start().column() + 1, 13); QCOMPARE(v[2].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[2].offset, size_t(6561)); - QCOMPARE(v[2].lenght, size_t(1)); + QCOMPARE(v[2].length, size_t(1)); QCOMPARE(v[2].replacementText, QString()); QCOMPARE(v[2].range.start().line() + 1, 162); QCOMPARE(v[2].range.start().column() + 1, 9); QCOMPARE(v[3].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[3].offset, size_t(6569)); - QCOMPARE(v[3].lenght, size_t(0)); + QCOMPARE(v[3].length, size_t(0)); QCOMPARE(v[3].replacementText, QStringLiteral(" == nullptr")); QCOMPARE(v[3].range.start().line() + 1, 162); QCOMPARE(v[3].range.start().column() + 1, 17); QCOMPARE(v[4].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[4].offset, size_t(7233)); - QCOMPARE(v[4].lenght, size_t(69)); + QCOMPARE(v[4].length, size_t(69)); QCOMPARE(v[4].replacementText, QStringLiteral("// TODO(cnihelton): auto detect clang-tidy executable" " instead of hard-coding it.")); QCOMPARE(v[4].range.start().line() + 1, 181); QCOMPARE(v[4].range.start().column() + 1, 5); QCOMPARE(v[5].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[5].offset, size_t(8800)); - QCOMPARE(v[5].lenght, size_t(4)); + QCOMPARE(v[5].length, size_t(4)); QCOMPARE(v[5].replacementText, QStringLiteral("auto ")); QCOMPARE(v[5].range.start().line() + 1, 210); QCOMPARE(v[5].range.start().column() + 1, 9); QCOMPARE(v[6].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[6].offset, size_t(8945)); - QCOMPARE(v[6].lenght, size_t(4)); + QCOMPARE(v[6].length, size_t(4)); QCOMPARE(v[6].replacementText, QStringLiteral("auto ")); QCOMPARE(v[6].range.start().line() + 1, 214); QCOMPARE(v[6].range.start().column() + 1, 5); QCOMPARE(v[7].range.document.str(), QStringLiteral("data/plugin.cpp")); QCOMPARE(v[7].offset, size_t(9406)); - QCOMPARE(v[7].lenght, size_t(1)); + QCOMPARE(v[7].length, size_t(1)); QCOMPARE(v[7].replacementText, QString()); QCOMPARE(v[7].range.start().line() + 1, 238); QCOMPARE(v[7].range.start().column() + 1, 9); + + // testing multibyte chars in source code. + ReplacementParser nihonParser("data/nihon_plugin.cpp.yaml", "data/nihon_plugin.cpp"); + nihonParser.parse(); + auto nv = nihonParser.allReplacements(); + QVERIFY(!nv.isEmpty()); + QCOMPARE(nv.length(), 17); + + QCOMPARE(nv[0].range.document.str(), QStringLiteral("data/nihon_plugin.cpp")); + QCOMPARE(nv[0].offset, size_t(10165)); + QCOMPARE(nv[0].length, size_t(1)); + QCOMPARE(nv[0].replacementText, QString()); + QCOMPARE(nv[0].range.start().line() + 1, 288); + QCOMPARE(nv[0].range.start().column() + 1, 9); + + QCOMPARE(nv[1].range.document.str(), QStringLiteral("data/nihon_plugin.cpp")); + QCOMPARE(nv[1].offset, size_t(10169)); + QCOMPARE(nv[1].length, size_t(0)); + QCOMPARE(nv[1].replacementText, QStringLiteral(" == nullptr")); + QCOMPARE(nv[1].range.start().line() + 1, 288); + QCOMPARE(nv[1].range.start().column() + 1, 13); + + QCOMPARE(nv[2].range.document.str(), QStringLiteral("data/nihon_plugin.cpp")); + QCOMPARE(nv[2].offset, size_t(10463)); + QCOMPARE(nv[2].length, size_t(1)); + QCOMPARE(nv[2].replacementText, QString()); + QCOMPARE(nv[2].range.start().line() + 1, 295); + QCOMPARE(nv[2].range.start().column() + 1, 9); + + QCOMPARE(nv[3].range.document.str(), QStringLiteral("data/nihon_plugin.cpp")); + QCOMPARE(nv[3].offset, size_t(10471)); + QCOMPARE(nv[3].length, size_t(0)); + QCOMPARE(nv[3].replacementText, QStringLiteral(" == nullptr")); + QCOMPARE(nv[3].range.start().line() + 1, 295); + QCOMPARE(nv[3].range.start().column() + 1, 17); + + QCOMPARE(nv[4].range.document.str(), QStringLiteral("data/nihon_plugin.cpp")); + QCOMPARE(nv[4].offset, size_t(11135)); + QCOMPARE(nv[4].length, size_t(69)); + QCOMPARE(nv[4].replacementText, QStringLiteral("// TODO(cnihelton): auto detect clang-tidy executable " + "instead of hard-coding it.")); + QCOMPARE(nv[4].range.start().line() + 1, 314); + QCOMPARE(nv[4].range.start().column() + 1, 5); } QTEST_GUILESS_MAIN(ReplacementsParserTester);