diff --git a/kdevplatform/outputview/outputfilteringstrategies.h b/kdevplatform/outputview/outputfilteringstrategies.h --- a/kdevplatform/outputview/outputfilteringstrategies.h +++ b/kdevplatform/outputview/outputfilteringstrategies.h @@ -83,12 +83,18 @@ { public: - ScriptErrorFilterStrategy(); + /** + * @param scriptDir The script working directory + */ + explicit ScriptErrorFilterStrategy(const QUrl& scriptDir); + ~ScriptErrorFilterStrategy() override; FilteredItem errorInLine(const QString& line) override; FilteredItem actionInLine(const QString& line) override; - +private: + const QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(ScriptErrorFilterStrategy) }; /** diff --git a/kdevplatform/outputview/outputfilteringstrategies.cpp b/kdevplatform/outputview/outputfilteringstrategies.cpp --- a/kdevplatform/outputview/outputfilteringstrategies.cpp +++ b/kdevplatform/outputview/outputfilteringstrategies.cpp @@ -20,7 +20,7 @@ #include "outputformats.h" #include "filtereditem.h" #include - +#include "debug.h" #include #include @@ -358,7 +358,26 @@ /// --- Script error filter strategy --- -ScriptErrorFilterStrategy::ScriptErrorFilterStrategy() +/// Impl. of ScriptErrorFilterStrategy. +class ScriptErrorFilterStrategyPrivate +{ +public: + explicit ScriptErrorFilterStrategyPrivate(const QUrl& scriptDir); + Path m_scriptDir; +}; + +ScriptErrorFilterStrategyPrivate::ScriptErrorFilterStrategyPrivate(const QUrl& scriptDir) + : m_scriptDir(scriptDir) +{ + if( !m_scriptDir.isValid() ) { + qCDebug(OUTPUTVIEW) << "Invalid scriptDir:" << scriptDir; + } +} + +ScriptErrorFilterStrategy::~ScriptErrorFilterStrategy() = default; + +ScriptErrorFilterStrategy::ScriptErrorFilterStrategy(const QUrl& scriptDir) + : d_ptr(new ScriptErrorFilterStrategyPrivate(scriptDir)) { } @@ -371,12 +390,94 @@ { // A list of filters for possible Python and PHP errors static const ErrorFormat SCRIPT_ERROR_FILTERS[] = { + // php trigger_error message + ErrorFormat( QStringLiteral(".*PHP\\s+(.*?):.* in\\s+(/.*) on line ([0-9]+)$"), 2, 3, 1, QStringLiteral("PHP") ), + // php trigger_error stack trace + // PHP 2. trigger_error() /home/proyect/yii:6 + ErrorFormat( QStringLiteral("^(PHP\\s+)[0-9]+\\.\\s+.*\\s+(/.*):([0-9]+)$"), 2, 3, 1, QStringLiteral("PHP") ), + // PHP Uncaught Error + ErrorFormat( QStringLiteral("^PHP.*(error):.* in (/.*):([0-9]+)$"), 2, 3, 1, QStringLiteral("PHP") ), + // General framework rendered Error (Yii2::renderException, etc.) + ErrorFormat( QStringLiteral("^([Ee]rror):.* in (/.*):([0-9]+)$"), 2, 3, 1, QStringLiteral("PHP") ), + // PHP Uncaught Error stack trace + // #0 /home/proyect/yii(6): bad() + ErrorFormat( QStringLiteral("^\\#[0-9]+\\s+(/.*)\\(([0-9]+)\\):.*$"), 1, 2, -1, QStringLiteral("PHP") ), + // PHP Uncaught Error stack trace, last thrown + ErrorFormat( QStringLiteral("^(thrown) in\\s+(/.*) on line ([0-9]+)$"), 2, 3, 1, QStringLiteral("PHP") ), + // Codeception summary failed tests + ErrorFormat( QStringLiteral("^\\s*Test\\s+(.*):.*$"), 1, -1, -1, QStringLiteral("CodeCeption") ), + // Codeception scenario steps + // 4. $I->grabFixture("model","models0") at tests/functional/ModelsCest.php:43 + ErrorFormat( QStringLiteral("^.* at (.*):([0-9]+)$"), 1, 2, -1, QStringLiteral("CodeCeption") ), + // Another stack trace format (seen with CodeCeption) + // #0 /home/proyect/yii:6 + ErrorFormat( QStringLiteral("^(\\#[0-9]*\\s)\\s*(/.*):([0-9]+)$"), 2, 3, 0, QStringLiteral("PHP") ), + // ungreedy with no trailing text, usually the output of the php function trigger_error() + ErrorFormat( QStringLiteral("^.*\\.\\s+(/.*?):([0-9]+)$"), 1, 2, 0, QStringLiteral("PHP") ), + // Codeception functional tests response + ErrorFormat( QStringLiteral("^\\s*(Response:)\\s+(/.*)$"), 2, -1, 1), ErrorFormat( QStringLiteral("^ File \"(.*)\", line ([0-9]+)(.*$|, in(.*)$)"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.*(/.*):([0-9]+).*$"), 1, 2, -1 ), - ErrorFormat( QStringLiteral("^.* in (/.*) on line ([0-9]+).*$"), 1, 2, -1 ) + ErrorFormat( QStringLiteral("^.* in (/.*) on line ([0-9]+).*$"), 1, 2, -1 ), + + }; + using Indicator = QPair; + static const Indicator INDICATORS[] = { + // PHP +// Indicator(QStringLiteral("Notice:"), FilteredItem::InformationItem), +// Indicator(QStringLiteral("thrown:"), FilteredItem::InformationItem), +// Indicator(QStringLiteral("PHP "), FilteredItem::InformationItem), + Indicator(QStringLiteral("Warning"), FilteredItem::WarningItem), + Indicator(QStringLiteral("error"), FilteredItem::ErrorItem), + Indicator(QStringLiteral("Error"), FilteredItem::ErrorItem), + Indicator(QStringLiteral("DEPRECATION"), FilteredItem::WarningItem), + Indicator(QStringLiteral("Response:"), FilteredItem::WarningItem), + Indicator(QStringLiteral("#1 "), FilteredItem::WarningItem), }; + FilteredItem item(line); + for (const auto& curErrFilter : SCRIPT_ERROR_FILTERS) { + const auto match = curErrFilter.expression.match(line); + if( match.hasMatch() ) + { + QString url; + if(curErrFilter.fileGroup > 0) { + url = match.captured( curErrFilter.fileGroup ); + QFileInfo fi(url); + if( fi.isRelative() ) { + item.url = Path(d_ptr->m_scriptDir, url).toUrl(); + } else { + item.url = Path(url).toUrl(); + } + } + item.type = FilteredItem::InformationItem; + initializeFilteredItem(item, curErrFilter, match); + const QString txt = match.captured(curErrFilter.textGroup); + + // Find the indicator which happens most early. + if( txt.length() ) { + int earliestIndicatorIdx = txt.length(); + for (const auto& curIndicator : INDICATORS) { + int curIndicatorIdx = txt.indexOf(curIndicator.first, 0, Qt::CaseInsensitive); + if((curIndicatorIdx >= 0) && (earliestIndicatorIdx > curIndicatorIdx)) { + earliestIndicatorIdx = curIndicatorIdx; + item.type = curIndicator.second; + } + } + } - return match(SCRIPT_ERROR_FILTERS, line); + // Make the item clickable if it comes with the necessary file information + if (item.url.isValid()) { + item.isActivatable = true; + if(item.type == FilteredItem::InvalidItem) { + // Okay so we couldn't find anything to indicate an error, but we have file and lineGroup + // Lets keep this item clickable and indicate this to the user. + item.type = FilteredItem::InformationItem; + } + } + break; + } + } + return item; } /// --- Native application error filter strategy --- diff --git a/kdevplatform/outputview/outputmodel.cpp b/kdevplatform/outputview/outputmodel.cpp --- a/kdevplatform/outputview/outputmodel.cpp +++ b/kdevplatform/outputview/outputmodel.cpp @@ -427,7 +427,7 @@ filter = new CompilerFilterStrategy( d->m_buildDir ); break; case ScriptErrorFilter: - filter = new ScriptErrorFilterStrategy; + filter = new ScriptErrorFilterStrategy( d->m_buildDir ); break; case NativeAppErrorFilter: filter = new NativeAppErrorFilterStrategy; diff --git a/kdevplatform/outputview/tests/test_filteringstrategy.cpp b/kdevplatform/outputview/tests/test_filteringstrategy.cpp --- a/kdevplatform/outputview/tests/test_filteringstrategy.cpp +++ b/kdevplatform/outputview/tests/test_filteringstrategy.cpp @@ -240,10 +240,16 @@ void TestFilteringStrategy::testScriptErrorFilterStrategy() { +#ifdef Q_OS_WIN + TestPathType pathTypeToUse = WindowsFilePathNoSpaces; +#else + TestPathType pathTypeToUse = UnixFilePathNoSpaces; +#endif QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); - ScriptErrorFilterStrategy testee; + QUrl scripturl = QUrl::fromLocalFile( projectPath(pathTypeToUse) ); + ScriptErrorFilterStrategy testee(scripturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); diff --git a/plugins/executescript/scriptappjob.cpp b/plugins/executescript/scriptappjob.cpp --- a/plugins/executescript/scriptappjob.cpp +++ b/plugins/executescript/scriptappjob.cpp @@ -129,9 +129,18 @@ auto currentFilterMode = static_cast( iface->outputFilterModeId( cfg ) ); + QUrl wc = iface->workingDirectory( cfg ); + if( !wc.isValid() || wc.isEmpty() ) + { + qCWarning(PLUGIN_EXECUTESCRIPT) << "Launch Configuration:" << cfg->name() << "no valid working directory: " << wc; + wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() ); + qCWarning(PLUGIN_EXECUTESCRIPT) << "Assuming working directory:" << wc; + } + QUrl localwc( ICore::self()->runtimeController()->currentRuntime()->pathInRuntime(KDevelop::Path(wc)).toUrl() ); + setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); - auto* m = new KDevelop::OutputModel; + auto* m = new KDevelop::OutputModel(localwc); m->setFilteringStrategy(currentFilterMode); setModel( m ); setDelegate( new KDevelop::OutputDelegate ); @@ -143,12 +152,7 @@ // Now setup the process parameters proc->setEnvironment(environmentProfiles.createEnvironment(envProfileName, proc->systemEnvironment())); - QUrl wc = iface->workingDirectory( cfg ); - if( !wc.isValid() || wc.isEmpty() ) - { - wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() ); - } - proc->setWorkingDirectory( ICore::self()->runtimeController()->currentRuntime()->pathInRuntime(KDevelop::Path(wc)).toLocalFile() ); + proc->setWorkingDirectory( localwc.toLocalFile() ); proc->setProperty( "executable", interpreter.first() ); QStringList program;