1 /***************************************************************************
2                           php_parser.cpp  -  description
3                              -------------------
4     begin         : Apr 1st 2007
5     last update   : Sep 14th 2010
6     author(s)     : 2007, Massimo Callegari <massimocallegari@yahoo.it>
7                   : 2010, Emmanuel Bouthenot <kolter@openics.org>
8  ***************************************************************************/
9 /***************************************************************************
10  *                                                                         *
11  *   SPDX-License-Identifier: GPL-2.0-or-later
12  *                                                                         *
13  ***************************************************************************/
14 
15 #include "plugin_katesymbolviewer.h"
16 
parsePhpSymbols(void)17 void KatePluginSymbolViewerView::parsePhpSymbols(void)
18 {
19     if (m_mainWindow->activeView()) {
20         QString line, lineWithliterals;
21         QPixmap namespacePix(class_int_xpm);
22         QPixmap definePix(macro_xpm);
23         QPixmap varPix(struct_xpm);
24         QPixmap classPix(class_xpm);
25         QPixmap constPix(macro_xpm);
26         QPixmap functionPix(method_xpm);
27         QTreeWidgetItem *node = nullptr;
28         QTreeWidgetItem *namespaceNode = nullptr, *defineNode = nullptr, *classNode = nullptr, *functionNode = nullptr;
29         QTreeWidgetItem *lastNamespaceNode = nullptr, *lastDefineNode = nullptr, *lastClassNode = nullptr, *lastFunctionNode = nullptr;
30 
31         KTextEditor::Document *kv = m_mainWindow->activeView()->document();
32 
33         if (m_treeOn->isChecked()) {
34             namespaceNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Namespaces")));
35             defineNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Defines")));
36             classNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Classes")));
37             functionNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Functions")));
38 
39             namespaceNode->setIcon(0, QIcon(namespacePix));
40             defineNode->setIcon(0, QIcon(definePix));
41             classNode->setIcon(0, QIcon(classPix));
42             functionNode->setIcon(0, QIcon(functionPix));
43 
44             if (m_expandOn->isChecked()) {
45                 m_symbols->expandItem(namespaceNode);
46                 m_symbols->expandItem(defineNode);
47                 m_symbols->expandItem(classNode);
48                 m_symbols->expandItem(functionNode);
49             }
50 
51             lastNamespaceNode = namespaceNode;
52             lastDefineNode = defineNode;
53             lastClassNode = classNode;
54             lastFunctionNode = functionNode;
55 
56             m_symbols->setRootIsDecorated(1);
57         } else {
58             m_symbols->setRootIsDecorated(0);
59         }
60 
61         // Namespaces: https://www.php.net/manual/en/language.namespaces.php
62         QRegExp namespaceRegExp(QLatin1String("^namespace\\s+([^;\\s]+)"), Qt::CaseInsensitive);
63         // defines: https://www.php.net/manual/en/function.define.php
64         QRegExp defineRegExp(QLatin1String("(^|\\W)define\\s*\\(\\s*['\"]([^'\"]+)['\"]"), Qt::CaseInsensitive);
65         // classes: https://www.php.net/manual/en/language.oop5.php
66         QRegExp classRegExp(QLatin1String("^((abstract\\s+|final\\s+)?)class\\s+([\\w_][\\w\\d_]*)\\s*(implements\\s+[\\w\\d_]*)?"), Qt::CaseInsensitive);
67         // interfaces: https://www.php.net/manual/en/language.oop5.php
68         QRegExp interfaceRegExp(QLatin1String("^interface\\s+([\\w_][\\w\\d_]*)"), Qt::CaseInsensitive);
69         // classes constants: https://www.php.net/manual/en/language.oop5.constants.php
70         QRegExp constantRegExp(QLatin1String("^const\\s+([\\w_][\\w\\d_]*)"), Qt::CaseInsensitive);
71         // functions: https://www.php.net/manual/en/language.oop5.constants.php
72         QRegExp functionRegExp(QLatin1String("^((public|protected|private)?(\\s*static)?\\s+)?function\\s+&?\\s*([\\w_][\\w\\d_]*)\\s*(.*)$"),
73                                Qt::CaseInsensitive);
74         // variables: https://www.php.net/manual/en/language.oop5.properties.php
75         QRegExp varRegExp(QLatin1String("^((var|public|protected|private)?(\\s*static)?\\s+)?\\$([\\w_][\\w\\d_]*)"), Qt::CaseInsensitive);
76 
77         // function args detection: “function a($b, $c=null)” => “$b, $v”
78         QRegExp functionArgsRegExp(QLatin1String("(\\$[\\w_]+)"), Qt::CaseInsensitive);
79         QStringList functionArgsList;
80         QString nameWithTypes;
81 
82         // replace literals by empty strings: “function a($b='nothing', $c="pretty \"cool\" string")” => “function ($b='', $c="")”
83         QRegExp literalRegExp(QLatin1String("([\"'])(?:\\\\.|[^\\\\])*\\1"));
84         literalRegExp.setMinimal(true);
85         // remove useless comments: “public/* static */ function a($b, $c=null) /* test */” => “public function a($b, $c=null)”
86         QRegExp blockCommentInline(QLatin1String("/\\*.*\\*/"));
87         blockCommentInline.setMinimal(true);
88 
89         int i, pos;
90         bool isClass, isInterface;
91         bool inBlockComment = false;
92         bool inClass = false, inFunction = false;
93 
94         // QString debugBuffer("SymbolViewer(PHP), line %1 %2 → [%3]");
95 
96         for (i = 0; i < kv->lines(); i++) {
97             // kdDebug(13000) << debugBuffer.arg(i, 4).arg("=origin", 10).arg(kv->line(i));
98 
99             line = kv->line(i).simplified();
100             // kdDebug(13000) << debugBuffer.arg(i, 4).arg("+simplified", 10).arg(line);
101 
102             // keeping a copy with literals for catching “defines()”
103             lineWithliterals = line;
104 
105             // reduce literals to empty strings to not match comments separators in literals
106             line.replace(literalRegExp, QLatin1String("\\1\\1"));
107             // kdDebug(13000) << debugBuffer.arg(i, 4).arg("-literals", 10).arg(line);
108 
109             line.remove(blockCommentInline);
110             // kdDebug(13000) << debugBuffer.arg(i, 4).arg("-comments", 10).arg(line);
111 
112             // trying to find comments and to remove commented parts
113             pos = line.indexOf(QLatin1Char('#'));
114             if (pos >= 0) {
115                 line.truncate(pos);
116             }
117             pos = line.indexOf(QLatin1String("//"));
118             if (pos >= 0) {
119                 line.truncate(pos);
120             }
121             pos = line.indexOf(QLatin1String("/*"));
122             if (pos >= 0) {
123                 line.truncate(pos);
124                 inBlockComment = true;
125             }
126             pos = line.indexOf(QLatin1String("*/"));
127             if (pos >= 0) {
128                 line = line.right(line.length() - pos - 2);
129                 inBlockComment = false;
130             }
131 
132             if (inBlockComment) {
133                 continue;
134             }
135 
136             // trimming again after having removed the comments
137             line = line.simplified();
138             // kdDebug(13000) << debugBuffer.arg(i, 4).arg("+simplified", 10).arg(line);
139 
140             // detect NameSpaces
141             if (namespaceRegExp.indexIn(line) != -1) {
142                 if (m_treeOn->isChecked()) {
143                     node = new QTreeWidgetItem(namespaceNode, lastNamespaceNode);
144                     if (m_expandOn->isChecked()) {
145                         m_symbols->expandItem(node);
146                     }
147                     lastNamespaceNode = node;
148                 } else {
149                     node = new QTreeWidgetItem(m_symbols);
150                 }
151                 node->setText(0, namespaceRegExp.cap(1));
152                 node->setIcon(0, QIcon(namespacePix));
153                 node->setText(1, QString::number(i, 10));
154             }
155 
156             // detect defines
157             if (defineRegExp.indexIn(lineWithliterals) != -1) {
158                 if (m_treeOn->isChecked()) {
159                     node = new QTreeWidgetItem(defineNode, lastDefineNode);
160                     lastDefineNode = node;
161                 } else {
162                     node = new QTreeWidgetItem(m_symbols);
163                 }
164                 node->setText(0, defineRegExp.cap(2));
165                 node->setIcon(0, QIcon(definePix));
166                 node->setText(1, QString::number(i, 10));
167             }
168 
169             // detect classes, interfaces
170             isClass = classRegExp.indexIn(line) != -1;
171             isInterface = interfaceRegExp.indexIn(line) != -1;
172             if (isClass || isInterface) {
173                 if (m_treeOn->isChecked()) {
174                     node = new QTreeWidgetItem(classNode, lastClassNode);
175                     if (m_expandOn->isChecked()) {
176                         m_symbols->expandItem(node);
177                     }
178                     lastClassNode = node;
179                 } else {
180                     node = new QTreeWidgetItem(m_symbols);
181                 }
182                 if (isClass) {
183                     if (m_typesOn->isChecked()) {
184                         if (!classRegExp.cap(1).trimmed().isEmpty() && !classRegExp.cap(4).trimmed().isEmpty()) {
185                             nameWithTypes = classRegExp.cap(3) + QLatin1String(" [") + classRegExp.cap(1).trimmed() + QLatin1Char(',')
186                                 + classRegExp.cap(4).trimmed() + QLatin1Char(']');
187                         } else if (!classRegExp.cap(1).trimmed().isEmpty()) {
188                             nameWithTypes = classRegExp.cap(3) + QLatin1String(" [") + classRegExp.cap(1).trimmed() + QLatin1Char(']');
189                         } else if (!classRegExp.cap(4).trimmed().isEmpty()) {
190                             nameWithTypes = classRegExp.cap(3) + QLatin1String(" [") + classRegExp.cap(4).trimmed() + QLatin1Char(']');
191                         }
192                         node->setText(0, nameWithTypes);
193                     } else {
194                         node->setText(0, classRegExp.cap(3));
195                     }
196                 } else {
197                     if (m_typesOn->isChecked()) {
198                         nameWithTypes = interfaceRegExp.cap(1) + QLatin1String(" [interface]");
199                         node->setText(0, nameWithTypes);
200                     } else {
201                         node->setText(0, interfaceRegExp.cap(1));
202                     }
203                 }
204                 node->setIcon(0, QIcon(classPix));
205                 node->setText(1, QString::number(i, 10));
206                 node->setToolTip(0, nameWithTypes);
207                 inClass = true;
208                 inFunction = false;
209             }
210 
211             // detect class constants
212             if (constantRegExp.indexIn(line) != -1) {
213                 if (m_treeOn->isChecked()) {
214                     node = new QTreeWidgetItem(lastClassNode);
215                 } else {
216                     node = new QTreeWidgetItem(m_symbols);
217                 }
218                 node->setText(0, constantRegExp.cap(1));
219                 node->setIcon(0, QIcon(constPix));
220                 node->setText(1, QString::number(i, 10));
221             }
222 
223             // detect class variables
224             if (inClass && !inFunction) {
225                 if (varRegExp.indexIn(line) != -1) {
226                     if (m_treeOn->isChecked() && inClass) {
227                         node = new QTreeWidgetItem(lastClassNode);
228                     } else {
229                         node = new QTreeWidgetItem(m_symbols);
230                     }
231                     node->setText(0, varRegExp.cap(4));
232                     node->setIcon(0, QIcon(varPix));
233                     node->setText(1, QString::number(i, 10));
234                 }
235             }
236 
237             // detect functions
238             if (functionRegExp.indexIn(line) != -1) {
239                 if (m_treeOn->isChecked() && inClass) {
240                     node = new QTreeWidgetItem(lastClassNode);
241                 } else if (m_treeOn->isChecked()) {
242                     node = new QTreeWidgetItem(lastFunctionNode);
243                 } else {
244                     node = new QTreeWidgetItem(m_symbols);
245                 }
246 
247                 QString functionArgs(functionRegExp.cap(5));
248                 pos = 0;
249                 while (pos >= 0) {
250                     pos = functionArgsRegExp.indexIn(functionArgs, pos);
251                     if (pos >= 0) {
252                         pos += functionArgsRegExp.matchedLength();
253                         functionArgsList += functionArgsRegExp.cap(1);
254                     }
255                 }
256 
257                 nameWithTypes = functionRegExp.cap(4) + QLatin1Char('(') + functionArgsList.join(QLatin1String(", ")) + QLatin1Char(')');
258                 if (m_typesOn->isChecked()) {
259                     node->setText(0, nameWithTypes);
260                 } else {
261                     node->setText(0, functionRegExp.cap(4));
262                 }
263 
264                 node->setIcon(0, QIcon(functionPix));
265                 node->setText(1, QString::number(i, 10));
266                 node->setToolTip(0, nameWithTypes);
267 
268                 functionArgsList.clear();
269 
270                 inFunction = true;
271             }
272         }
273     }
274 }
275