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 }