php_parser.cpp 13.2 KB
Newer Older
Massimo Callegari's avatar
Massimo Callegari committed
1
2
3
/***************************************************************************
                          php_parser.cpp  -  description
                             -------------------
4
5
6
7
    begin         : Apr 1st 2007
    last update   : Sep 14th 2010
    author(s)     : 2007, Massimo Callegari <massimocallegari@yahoo.it>
                  : 2010, Emmanuel Bouthenot <kolter@openics.org>
Massimo Callegari's avatar
Massimo Callegari committed
8
 ***************************************************************************/
9
/***************************************************************************
Massimo Callegari's avatar
Massimo Callegari committed
10
 *                                                                         *
11
 *   SPDX-License-Identifier: GPL-2.0-or-later
Massimo Callegari's avatar
Massimo Callegari committed
12
13
 *                                                                         *
 ***************************************************************************/
14

Massimo Callegari's avatar
Massimo Callegari committed
15
#include "plugin_katesymbolviewer.h"
16
#include <QRegularExpression>
Massimo Callegari's avatar
Massimo Callegari committed
17

18
void KatePluginSymbolViewerView::parsePhpSymbols(void)
19
{
20
21
    if (m_mainWindow->activeView()) {
        QString line, lineWithliterals;
22
23
24
25
26
27
        QPixmap namespacePix(class_int_xpm);
        QPixmap definePix(macro_xpm);
        QPixmap varPix(struct_xpm);
        QPixmap classPix(class_xpm);
        QPixmap constPix(macro_xpm);
        QPixmap functionPix(method_xpm);
28
29
30
        QTreeWidgetItem *node = nullptr;
        QTreeWidgetItem *namespaceNode = nullptr, *defineNode = nullptr, *classNode = nullptr, *functionNode = nullptr;
        QTreeWidgetItem *lastNamespaceNode = nullptr, *lastDefineNode = nullptr, *lastClassNode = nullptr, *lastFunctionNode = nullptr;
31

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
        KTextEditor::Document *kv = m_mainWindow->activeView()->document();

        if (m_treeOn->isChecked()) {
            namespaceNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Namespaces")));
            defineNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Defines")));
            classNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Classes")));
            functionNode = new QTreeWidgetItem(m_symbols, QStringList(i18n("Functions")));

            namespaceNode->setIcon(0, QIcon(namespacePix));
            defineNode->setIcon(0, QIcon(definePix));
            classNode->setIcon(0, QIcon(classPix));
            functionNode->setIcon(0, QIcon(functionPix));

            if (m_expandOn->isChecked()) {
                m_symbols->expandItem(namespaceNode);
                m_symbols->expandItem(defineNode);
                m_symbols->expandItem(classNode);
                m_symbols->expandItem(functionNode);
            }

            lastNamespaceNode = namespaceNode;
            lastDefineNode = defineNode;
            lastClassNode = classNode;
            lastFunctionNode = functionNode;

            m_symbols->setRootIsDecorated(1);
        } else {
            m_symbols->setRootIsDecorated(0);
60
        }
61

62
        // Namespaces: https://www.php.net/manual/en/language.namespaces.php
63
        static const QRegularExpression namespaceRegExp(QLatin1String("^namespace\\s+([^;\\s]+)"), QRegularExpression::CaseInsensitiveOption);
64
        // defines: https://www.php.net/manual/en/function.define.php
65
        static const QRegularExpression defineRegExp(QLatin1String("(^|\\W)define\\s*\\(\\s*['\"]([^'\"]+)['\"]"), QRegularExpression::CaseInsensitiveOption);
66
        // classes: https://www.php.net/manual/en/language.oop5.php
67
        static const QRegularExpression classRegExp(QLatin1String("^((abstract\\s+|final\\s+)?)class\\s+([\\w_][\\w\\d_]*)\\s*(implements\\s+[\\w\\d_]*|extends\\s+[\\w\\d_]*)?"), QRegularExpression::CaseInsensitiveOption);
68
        // interfaces: https://www.php.net/manual/en/language.oop5.php
69
        static const QRegularExpression interfaceRegExp(QLatin1String("^interface\\s+([\\w_][\\w\\d_]*)"), QRegularExpression::CaseInsensitiveOption);
70
        // classes constants: https://www.php.net/manual/en/language.oop5.constants.php
71
        static const QRegularExpression constantRegExp(QLatin1String("^const\\s+([\\w_][\\w\\d_]*)"), QRegularExpression::CaseInsensitiveOption);
72
        // functions: https://www.php.net/manual/en/language.oop5.constants.php
73
        static const QRegularExpression functionRegExp(QLatin1String("^((public|protected|private)?(\\s*static)?\\s+)?function\\s+&?\\s*([\\w_][\\w\\d_]*)\\s*(.*)$"), QRegularExpression::CaseInsensitiveOption);
74
        // variables: https://www.php.net/manual/en/language.oop5.properties.php
75
        static const QRegularExpression varRegExp(QLatin1String("^((var|public|protected|private)?(\\s*static)?\\s+)?\\$([\\w_][\\w\\d_]*)"), QRegularExpression::CaseInsensitiveOption);
76
77

        // function args detection: “function a($b, $c=null)” => “$b, $v”
78
        static const QRegularExpression functionArgsRegExp(QLatin1String("(\\$[\\w_]+)"), QRegularExpression::CaseInsensitiveOption);
79
80
81
82
        QStringList functionArgsList;
        QString nameWithTypes;

        // replace literals by empty strings: “function a($b='nothing', $c="pretty \"cool\" string")” => “function ($b='', $c="")”
83
        static const QRegularExpression literalRegExp(QLatin1String("([\"'])(?:\\\\.|[^\\\\])*\\1"), QRegularExpression::InvertedGreedinessOption);
84
        // remove useless comments: “public/* static */ function a($b, $c=null) /* test */” => “public function a($b, $c=null)”
85
86
87
        static const QRegularExpression blockCommentInline(QLatin1String("/\\*.*\\*/"), QRegularExpression::InvertedGreedinessOption);

        QRegularExpressionMatch match, matchClass, matchInterface, matchFunctionArgs;
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

        int i, pos;
        bool inBlockComment = false;
        bool inClass = false, inFunction = false;

        // QString debugBuffer("SymbolViewer(PHP), line %1 %2 → [%3]");

        for (i = 0; i < kv->lines(); i++) {
            // kdDebug(13000) << debugBuffer.arg(i, 4).arg("=origin", 10).arg(kv->line(i));

            line = kv->line(i).simplified();
            // kdDebug(13000) << debugBuffer.arg(i, 4).arg("+simplified", 10).arg(line);

            // keeping a copy with literals for catching “defines()”
            lineWithliterals = line;

            // reduce literals to empty strings to not match comments separators in literals
Christoph Cullmann's avatar
Christoph Cullmann committed
105
            line.replace(literalRegExp, QLatin1String("\\1\\1"));
106

Christoph Cullmann's avatar
Christoph Cullmann committed
107
108
109
            // kdDebug(13000) << debugBuffer.arg(i, 4).arg("-literals", 10).arg(line);
            line.remove(blockCommentInline);

110
111
112
113
114
115
            // kdDebug(13000) << debugBuffer.arg(i, 4).arg("-comments", 10).arg(line);

            // trying to find comments and to remove commented parts
            pos = line.indexOf(QLatin1Char('#'));
            if (pos >= 0) {
                line.truncate(pos);
116
            }
117
118
119
            pos = line.indexOf(QLatin1String("//"));
            if (pos >= 0) {
                line.truncate(pos);
120
            }
121
122
123
124
125
126
127
128
129
            pos = line.indexOf(QLatin1String("/*"));
            if (pos >= 0) {
                line.truncate(pos);
                inBlockComment = true;
            }
            pos = line.indexOf(QLatin1String("*/"));
            if (pos >= 0) {
                line = line.right(line.length() - pos - 2);
                inBlockComment = false;
130
            }
Massimo Callegari's avatar
Massimo Callegari committed
131

132
133
134
135
136
137
138
139
140
            if (inBlockComment) {
                continue;
            }

            // trimming again after having removed the comments
            line = line.simplified();
            // kdDebug(13000) << debugBuffer.arg(i, 4).arg("+simplified", 10).arg(line);

            // detect NameSpaces
141
142
            match = namespaceRegExp.match(line);
            if (match.hasMatch()) {
143
144
145
146
147
148
149
150
151
                if (m_treeOn->isChecked()) {
                    node = new QTreeWidgetItem(namespaceNode, lastNamespaceNode);
                    if (m_expandOn->isChecked()) {
                        m_symbols->expandItem(node);
                    }
                    lastNamespaceNode = node;
                } else {
                    node = new QTreeWidgetItem(m_symbols);
                }
152
                node->setText(0, match.captured(1));
153
154
155
156
157
                node->setIcon(0, QIcon(namespacePix));
                node->setText(1, QString::number(i, 10));
            }

            // detect defines
158
159
            match = defineRegExp.match(lineWithliterals);
            if (match.hasMatch()) {
160
161
162
163
164
165
                if (m_treeOn->isChecked()) {
                    node = new QTreeWidgetItem(defineNode, lastDefineNode);
                    lastDefineNode = node;
                } else {
                    node = new QTreeWidgetItem(m_symbols);
                }
166
                node->setText(0, match.captured(2));
167
168
169
170
171
                node->setIcon(0, QIcon(definePix));
                node->setText(1, QString::number(i, 10));
            }

            // detect classes, interfaces
172
173
174
            matchClass = classRegExp.match(line);
            matchInterface = interfaceRegExp.match(line);
            if (matchClass.hasMatch() || matchInterface.hasMatch()) {
175
176
177
178
179
180
181
182
183
                if (m_treeOn->isChecked()) {
                    node = new QTreeWidgetItem(classNode, lastClassNode);
                    if (m_expandOn->isChecked()) {
                        m_symbols->expandItem(node);
                    }
                    lastClassNode = node;
                } else {
                    node = new QTreeWidgetItem(m_symbols);
                }
184
                if (matchClass.hasMatch()) {
185
                    if (m_typesOn->isChecked()) {
186
187
188
189
190
191
192
                        if (!matchClass.captured(1).trimmed().isEmpty() && !matchClass.captured(4).trimmed().isEmpty()) {
                            nameWithTypes = matchClass.captured(3) + QLatin1String(" [") + matchClass.captured(1).trimmed() + QLatin1Char(',')
                                + matchClass.captured(4).trimmed() + QLatin1Char(']');
                        } else if (!matchClass.captured(1).trimmed().isEmpty()) {
                            nameWithTypes = matchClass.captured(3) + QLatin1String(" [") + matchClass.captured(1).trimmed() + QLatin1Char(']');
                        } else if (!matchClass.captured(4).trimmed().isEmpty()) {
                            nameWithTypes = matchClass.captured(3) + QLatin1String(" [") + matchClass.captured(4).trimmed() + QLatin1Char(']');
193
194
                        } else {
                            nameWithTypes = matchClass.captured(3);
195
196
197
                        }
                        node->setText(0, nameWithTypes);
                    } else {
198
                        node->setText(0, matchClass.captured(3));
199
200
201
                    }
                } else {
                    if (m_typesOn->isChecked()) {
202
                        nameWithTypes = matchInterface.captured(1) + QLatin1String(" [interface]");
203
204
                        node->setText(0, nameWithTypes);
                    } else {
205
                        node->setText(0, matchInterface.captured(1));
206
207
208
209
210
211
212
213
214
215
                    }
                }
                node->setIcon(0, QIcon(classPix));
                node->setText(1, QString::number(i, 10));
                node->setToolTip(0, nameWithTypes);
                inClass = true;
                inFunction = false;
            }

            // detect class constants
216
217
            match = constantRegExp.match(line);
            if (match.hasMatch()) {
218
219
220
221
222
                if (m_treeOn->isChecked()) {
                    node = new QTreeWidgetItem(lastClassNode);
                } else {
                    node = new QTreeWidgetItem(m_symbols);
                }
223
                node->setText(0, match.captured(1));
224
225
226
227
228
229
                node->setIcon(0, QIcon(constPix));
                node->setText(1, QString::number(i, 10));
            }

            // detect class variables
            if (inClass && !inFunction) {
230
231
                match = varRegExp.match(line);
                if (match.hasMatch()) {
232
233
234
235
236
                    if (m_treeOn->isChecked() && inClass) {
                        node = new QTreeWidgetItem(lastClassNode);
                    } else {
                        node = new QTreeWidgetItem(m_symbols);
                    }
237
                    node->setText(0, match.captured(4));
238
239
240
241
242
243
                    node->setIcon(0, QIcon(varPix));
                    node->setText(1, QString::number(i, 10));
                }
            }

            // detect functions
244
245
            match = functionRegExp.match(line);
            if (match.hasMatch()) {
246
247
248
249
250
251
252
253
                if (m_treeOn->isChecked() && inClass) {
                    node = new QTreeWidgetItem(lastClassNode);
                } else if (m_treeOn->isChecked()) {
                    node = new QTreeWidgetItem(lastFunctionNode);
                } else {
                    node = new QTreeWidgetItem(m_symbols);
                }

254
255
256
                QString functionArgs(match.captured(5));
                matchFunctionArgs = functionArgsRegExp.match(functionArgs);
                functionArgsList = matchFunctionArgs.capturedTexts();
257

258
                nameWithTypes = match.captured(4) + QLatin1Char('(') + functionArgsList.join(QLatin1String(", ")) + QLatin1Char(')');
259
260
261
                if (m_typesOn->isChecked()) {
                    node->setText(0, nameWithTypes);
                } else {
262
                    node->setText(0, match.captured(4));
263
264
265
266
267
268
269
270
271
272
                }

                node->setIcon(0, QIcon(functionPix));
                node->setText(1, QString::number(i, 10));
                node->setToolTip(0, nameWithTypes);

                functionArgsList.clear();

                inFunction = true;
            }
273
274
275
        }
    }
}