File indexing completed on 2024-04-28 05:49:17

0001 /***************************************************************************
0002                           php_parser.cpp  -  description
0003                              -------------------
0004     begin         : Apr 1st 2007
0005     last update   : Sep 14th 2010
0006     author(s)     : 2007, Massimo Callegari <massimocallegari@yahoo.it>
0007                   : 2010, Emmanuel Bouthenot <kolter@openics.org>
0008  ***************************************************************************/
0009 /***************************************************************************
0010  *                                                                         *
0011  *   SPDX-License-Identifier: GPL-2.0-or-later
0012  *                                                                         *
0013  ***************************************************************************/
0014 
0015 #include "plugin_katesymbolviewer.h"
0016 #include <QRegularExpression>
0017 
0018 void KatePluginSymbolViewerView::parsePhpSymbols(void)
0019 {
0020     if (!m_mainWindow->activeView()) {
0021         return;
0022     }
0023 
0024     QTreeWidgetItem *node = nullptr;
0025     QTreeWidgetItem *namespaceNode = nullptr, *defineNode = nullptr, *classNode = nullptr, *functionNode = nullptr;
0026     QTreeWidgetItem *lastNamespaceNode = nullptr, *lastDefineNode = nullptr, *lastClassNode = nullptr, *lastFunctionNode = nullptr;
0027 
0028     KTextEditor::Document *kv = m_mainWindow->activeView()->document();
0029 
0030     if (m_treeOn->isChecked()) {
0031         namespaceNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Namespaces")));
0032         defineNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Defines")));
0033         classNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Classes")));
0034         functionNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Functions")));
0035 
0036         namespaceNode->setIcon(0, m_icon_context);
0037         defineNode->setIcon(0, m_icon_typedef);
0038         classNode->setIcon(0, m_icon_class);
0039         functionNode->setIcon(0, m_icon_function);
0040 
0041         if (m_expandOn->isChecked()) {
0042             m_symbols->expandItem(namespaceNode);
0043             m_symbols->expandItem(defineNode);
0044             m_symbols->expandItem(classNode);
0045             m_symbols->expandItem(functionNode);
0046         }
0047 
0048         lastNamespaceNode = namespaceNode;
0049         lastDefineNode = defineNode;
0050         lastClassNode = classNode;
0051         lastFunctionNode = functionNode;
0052 
0053         m_symbols->setRootIsDecorated(1);
0054     } else {
0055         m_symbols->setRootIsDecorated(0);
0056     }
0057 
0058     // Namespaces: https://www.php.net/manual/en/language.namespaces.php
0059     static const QRegularExpression namespaceRegExp(QLatin1String("^namespace\\s+([^;\\s]+)"), QRegularExpression::CaseInsensitiveOption);
0060     // defines: https://www.php.net/manual/en/function.define.php
0061     static const QRegularExpression defineRegExp(QLatin1String("(^|\\W)define\\s*\\(\\s*['\"]([^'\"]+)['\"]"), QRegularExpression::CaseInsensitiveOption);
0062     // classes: https://www.php.net/manual/en/language.oop5.php
0063     static const QRegularExpression classRegExp(
0064         QLatin1String("^((abstract\\s+|final\\s+)?)class\\s+([\\w_][\\w\\d_]*)\\s*(implements\\s+[\\w\\d_]*|extends\\s+[\\w\\d_]*)?"),
0065         QRegularExpression::CaseInsensitiveOption);
0066     // interfaces: https://www.php.net/manual/en/language.oop5.interfaces.php
0067     static const QRegularExpression interfaceRegExp(QLatin1String("^interface\\s+([\\w_][\\w\\d_]*)"), QRegularExpression::CaseInsensitiveOption);
0068     // traits: https://www.php.net/manual/en/language.oop5.traits.php
0069     static const QRegularExpression traitRegExp(QLatin1String("^trait\\s+([\\w_][\\w\\d_]*)"), QRegularExpression::CaseInsensitiveOption);
0070     // classes constants: https://www.php.net/manual/en/language.oop5.constants.php
0071     static const QRegularExpression constantRegExp(QLatin1String("^const\\s+([\\w_][\\w\\d_]*)"), QRegularExpression::CaseInsensitiveOption);
0072     // functions: https://www.php.net/manual/en/language.oop5.constants.php
0073     static const QRegularExpression functionRegExp(
0074         QLatin1String("^(\\s*)((public|protected|private)?(\\s*static)?\\s+)?function\\s+&?\\s*([\\w_][\\w\\d_]*)\\s*(.*)$"),
0075         QRegularExpression::CaseInsensitiveOption);
0076     // variables: https://www.php.net/manual/en/language.oop5.properties.php
0077     static const QRegularExpression varRegExp(QLatin1String("^((var|public|protected|private)?(\\s*static)?\\s+)?\\$([\\w_][\\w\\d_]*)"),
0078                                               QRegularExpression::CaseInsensitiveOption);
0079 
0080     // function args detection: “function a($b, $c=null)” => “$b, $v”
0081     static const QRegularExpression functionArgsRegExp(QLatin1String("(\\$[\\w_]+)"), QRegularExpression::CaseInsensitiveOption);
0082     QStringList functionArgsList;
0083     QString nameWithTypes;
0084 
0085     // replace literals by empty strings: “function a($b='nothing', $c="pretty \"cool\" string")” => “function ($b='', $c="")”
0086     static const QRegularExpression literalRegExp(QLatin1String("([\"'])(?:\\\\.|[^\\\\])*\\1"), QRegularExpression::InvertedGreedinessOption);
0087     // remove useless comments: “public/* static */ function a($b, $c=null) /* test */” => “public function a($b, $c=null)”
0088     static const QRegularExpression blockCommentInline(QLatin1String("/\\*.*\\*/"), QRegularExpression::InvertedGreedinessOption);
0089 
0090     QRegularExpressionMatch match, matchClass, matchInterface, matchTrait, matchFunctionArg;
0091     QRegularExpressionMatchIterator matchFunctionArgs;
0092 
0093     bool inBlockComment = false;
0094     bool inClass = false, inFunction = false;
0095 
0096     // QString debugBuffer("SymbolViewer(PHP), line %1 %2 → [%3]");
0097 
0098     for (int i = 0; i < kv->lines(); i++) {
0099         // kdDebug(13000) << debugBuffer.arg(i, 4).arg("=origin", 10).arg(kv->line(i));
0100 
0101         // keeping a copy of the line without any processing
0102         QString realLine = kv->line(i);
0103 
0104         QString line = realLine.simplified();
0105 
0106         // kdDebug(13000) << debugBuffer.arg(i, 4).arg("+simplified", 10).arg(line);
0107 
0108         // keeping a copy with literals for catching “defines()”
0109         QString lineWithliterals = line;
0110 
0111         // reduce literals to empty strings to not match comments separators in literals
0112         line.replace(literalRegExp, QLatin1String("\\1\\1"));
0113 
0114         // kdDebug(13000) << debugBuffer.arg(i, 4).arg("-literals", 10).arg(line);
0115         line.remove(blockCommentInline);
0116 
0117         // kdDebug(13000) << debugBuffer.arg(i, 4).arg("-comments", 10).arg(line);
0118 
0119         // trying to find comments and to remove commented parts
0120         if (const int pos = line.indexOf(QLatin1Char('#')); pos >= 0) {
0121             line.truncate(pos);
0122         }
0123         if (const int pos = line.indexOf(QLatin1String("//")); pos >= 0) {
0124             line.truncate(pos);
0125         }
0126         if (const int pos = line.indexOf(QLatin1String("/*")); pos >= 0) {
0127             line.truncate(pos);
0128             inBlockComment = true;
0129         }
0130         if (const int pos = line.indexOf(QLatin1String("*/")); pos >= 0) {
0131             line = line.right(line.length() - pos - 2);
0132             inBlockComment = false;
0133         }
0134 
0135         if (inBlockComment) {
0136             continue;
0137         }
0138 
0139         // trimming again after having removed the comments
0140         line = line.simplified();
0141         // kdDebug(13000) << debugBuffer.arg(i, 4).arg("+simplified", 10).arg(line);
0142 
0143         // detect NameSpaces
0144         match = namespaceRegExp.match(line);
0145         if (match.hasMatch()) {
0146             if (m_treeOn->isChecked()) {
0147                 node = new QTreeWidgetItem(namespaceNode, lastNamespaceNode);
0148                 if (m_expandOn->isChecked()) {
0149                     m_symbols->expandItem(node);
0150                 }
0151                 lastNamespaceNode = node;
0152             } else {
0153                 node = new QTreeWidgetItem(m_symbols);
0154             }
0155             node->setText(0, match.captured(1));
0156             node->setIcon(0, m_icon_context);
0157             node->setText(1, QString::number(i, 10));
0158         }
0159 
0160         // detect defines
0161         match = defineRegExp.match(lineWithliterals);
0162         if (match.hasMatch()) {
0163             if (m_treeOn->isChecked()) {
0164                 node = new QTreeWidgetItem(defineNode, lastDefineNode);
0165                 lastDefineNode = node;
0166             } else {
0167                 node = new QTreeWidgetItem(m_symbols);
0168             }
0169             node->setText(0, match.captured(2));
0170             node->setIcon(0, m_icon_typedef);
0171             node->setText(1, QString::number(i, 10));
0172         }
0173 
0174         // detect classes, interfaces and trait
0175         matchClass = classRegExp.match(line);
0176         matchInterface = interfaceRegExp.match(line);
0177         matchTrait = traitRegExp.match(line);
0178         if (matchClass.hasMatch() || matchInterface.hasMatch() || matchTrait.hasMatch()) {
0179             if (m_treeOn->isChecked()) {
0180                 node = new QTreeWidgetItem(classNode, lastClassNode);
0181                 if (m_expandOn->isChecked()) {
0182                     m_symbols->expandItem(node);
0183                 }
0184                 lastClassNode = node;
0185             } else {
0186                 node = new QTreeWidgetItem(m_symbols);
0187             }
0188             if (matchClass.hasMatch()) {
0189                 if (m_typesOn->isChecked()) {
0190                     nameWithTypes = matchClass.captured(3);
0191                     if (!matchClass.captured(1).trimmed().isEmpty() && !matchClass.captured(4).trimmed().isEmpty()) {
0192                         nameWithTypes +=
0193                             QLatin1String(" [") + matchClass.captured(1).trimmed() + QLatin1Char(',') + matchClass.captured(4).trimmed() + QLatin1Char(']');
0194                     } else if (!matchClass.captured(1).trimmed().isEmpty()) {
0195                         nameWithTypes += QLatin1String(" [") + matchClass.captured(1).trimmed() + QLatin1Char(']');
0196                     } else if (!matchClass.captured(4).trimmed().isEmpty()) {
0197                         nameWithTypes += QLatin1String(" [") + matchClass.captured(4).trimmed() + QLatin1Char(']');
0198                     }
0199                     node->setText(0, nameWithTypes);
0200                 } else {
0201                     node->setText(0, matchClass.captured(3));
0202                 }
0203             } else if (matchInterface.hasMatch()) {
0204                 if (m_typesOn->isChecked()) {
0205                     nameWithTypes = matchInterface.captured(1) + QLatin1String(" [interface]");
0206                     node->setText(0, nameWithTypes);
0207                 } else {
0208                     node->setText(0, matchInterface.captured(1));
0209                 }
0210             } else {
0211                 if (m_typesOn->isChecked()) {
0212                     nameWithTypes = matchTrait.captured(1) + QLatin1String(" [trait]");
0213                     node->setText(0, nameWithTypes);
0214                 } else {
0215                     node->setText(0, matchTrait.captured(1));
0216                 }
0217             }
0218             node->setIcon(0, m_icon_class);
0219             node->setText(1, QString::number(i, 10));
0220             node->setToolTip(0, nameWithTypes);
0221             inClass = true;
0222             inFunction = false;
0223         }
0224 
0225         // detect class constants
0226         match = constantRegExp.match(line);
0227         if (match.hasMatch()) {
0228             if (m_treeOn->isChecked()) {
0229                 node = new QTreeWidgetItem(lastClassNode);
0230             } else {
0231                 node = new QTreeWidgetItem(m_symbols);
0232             }
0233             node->setText(0, match.captured(1));
0234             node->setIcon(0, m_icon_typedef);
0235             node->setText(1, QString::number(i, 10));
0236         }
0237 
0238         // detect class variables
0239         if (inClass && !inFunction) {
0240             match = varRegExp.match(line);
0241             if (match.hasMatch()) {
0242                 if (m_treeOn->isChecked()) {
0243                     node = new QTreeWidgetItem(lastClassNode);
0244                 } else {
0245                     node = new QTreeWidgetItem(m_symbols);
0246                 }
0247                 node->setText(0, match.captured(4));
0248                 node->setIcon(0, m_icon_variable);
0249                 node->setText(1, QString::number(i, 10));
0250             }
0251         }
0252 
0253         // detect functions
0254         match = functionRegExp.match(realLine);
0255         if (match.hasMatch()) {
0256             if (m_treeOn->isChecked()) {
0257                 if (match.captured(1).isEmpty() && match.captured(2).isEmpty()) {
0258                     inClass = false;
0259                     node = new QTreeWidgetItem(lastFunctionNode);
0260                 } else {
0261                     node = new QTreeWidgetItem(lastClassNode);
0262                 }
0263             } else {
0264                 node = new QTreeWidgetItem(m_symbols);
0265             }
0266 
0267             QString functionArgs(match.captured(6));
0268             functionArgsList.clear();
0269             matchFunctionArgs = functionArgsRegExp.globalMatch(functionArgs);
0270             while (matchFunctionArgs.hasNext()) {
0271                 matchFunctionArg = matchFunctionArgs.next();
0272                 functionArgsList.append(matchFunctionArg.captured(0));
0273             }
0274 
0275             nameWithTypes = match.captured(5) + QLatin1Char('(') + functionArgsList.join(QLatin1String(", ")) + QLatin1Char(')');
0276             if (m_typesOn->isChecked()) {
0277                 node->setText(0, nameWithTypes);
0278             } else {
0279                 node->setText(0, match.captured(5));
0280             }
0281 
0282             node->setIcon(0, m_icon_function);
0283             node->setText(1, QString::number(i, 10));
0284             node->setToolTip(0, nameWithTypes);
0285 
0286             functionArgsList.clear();
0287 
0288             inFunction = true;
0289         }
0290     }
0291 }