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