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

0001 /***************************************************************************
0002                           ecma_parser.cpp  -  description
0003                              -------------------
0004     begin                : Feb 10 2012
0005     author               : 2012 Jesse Crossen
0006     email                : jesse.crossen@gmail.com
0007  ***************************************************************************/
0008 /***************************************************************************
0009  *                                                                         *
0010  *   SPDX-License-Identifier: GPL-2.0-or-later
0011  *                                                                         *
0012  ***************************************************************************/
0013 #include "plugin_katesymbolviewer.h"
0014 
0015 void KatePluginSymbolViewerView::parseEcmaSymbols(void)
0016 {
0017     // make sure there is an active view to attach to
0018     if (!m_mainWindow->activeView()) {
0019         return;
0020     }
0021 
0022     // a parsed class/function identifier
0023     QString identifier;
0024     // temporary characters
0025     QChar current, next, string_start = QLatin1Char('\0');
0026     // whether we are in a multiline comment
0027     bool in_comment = false;
0028     // indices into the string
0029     int c, function_start = 0;
0030     // a list of inserted nodes with the index being the brace depth at insertion
0031     QList<QTreeWidgetItem *> nodes;
0032 
0033     QTreeWidgetItem *node = nullptr;
0034 
0035     if (m_treeOn->isChecked()) {
0036         m_symbols->setRootIsDecorated(1);
0037     } else {
0038         m_symbols->setRootIsDecorated(0);
0039     }
0040 
0041     // read the document line by line
0042     KTextEditor::Document *kv = m_mainWindow->activeView()->document();
0043     for (int i = 0; i < kv->lines(); i++) {
0044         // get a line to process, trimming off whitespace
0045         QString cl = kv->line(i).trimmed();
0046         QString stripped; // the current line stripped of all comments and strings
0047         bool in_string = false;
0048         for (c = 0; c < cl.length(); c++) {
0049             // get the current character and the next
0050             current = cl.at(c);
0051             if ((c + 1) < cl.length()) {
0052                 next = cl.at(c + 1);
0053             } else {
0054                 next = QLatin1Char('\0');
0055             }
0056             // skip the rest of the line if we find a line comment
0057             if ((!in_comment) && (current == QLatin1Char('/')) && (next == QLatin1Char('/'))) {
0058                 break;
0059             }
0060             // open/close multiline comments
0061             if ((!in_string) && (current == QLatin1Char('/')) && (next == QLatin1Char('*'))) {
0062                 in_comment = true;
0063                 c++;
0064                 continue;
0065             } else if ((in_comment) && (current == QLatin1Char('*')) && (next == QLatin1Char('/'))) {
0066                 in_comment = false;
0067                 c++;
0068                 continue;
0069             }
0070             // open strings
0071             if ((!in_comment) && (!in_string)) {
0072                 if ((current == QLatin1Char('\'')) || (current == QLatin1Char('"'))) {
0073                     string_start = current;
0074                     in_string = true;
0075                     continue;
0076                 }
0077             }
0078             // close strings
0079             if (in_string) {
0080                 // skip escaped backslashes
0081                 if ((current == QLatin1Char('\\')) && (next == QLatin1Char('\\'))) {
0082                     c++;
0083                     continue;
0084                 }
0085                 // skip escaped string closures
0086                 if ((current == QLatin1Char('\\')) && (next == string_start)) {
0087                     c++;
0088                     continue;
0089                 } else if (current == string_start) {
0090                     in_string = false;
0091                     continue;
0092                 }
0093             }
0094             // add anything outside strings and comments to the stripped line
0095             if ((!in_comment) && (!in_string)) {
0096                 stripped += current;
0097             }
0098         }
0099 
0100         // scan the stripped line
0101         for (c = 0; c < stripped.length(); c++) {
0102             current = stripped.at(c);
0103 
0104             // look for class definitions (for ActionScript)
0105             if ((current == QLatin1Char('c')) && (stripped.indexOf(QLatin1String("class "), c) == c)) {
0106                 identifier.clear();
0107                 c += 6;
0108                 for (/*c = c*/; c < stripped.length(); c++) {
0109                     current = stripped.at(c);
0110                     // look for the beginning of the class itself
0111                     if ((current == QLatin1Char('(')) || (current == QLatin1Char('{'))) {
0112                         c--;
0113                         break;
0114                     } else {
0115                         identifier += current;
0116                     }
0117                 }
0118                 // trim whitespace
0119                 identifier = identifier.trimmed();
0120                 // get the node to add the class entry to
0121                 if ((m_treeOn->isChecked()) && (!nodes.isEmpty())) {
0122                     node = new QTreeWidgetItem(nodes.last());
0123                     if (m_expandOn->isChecked()) {
0124                         m_symbols->expandItem(node);
0125                     }
0126                 } else {
0127                     node = new QTreeWidgetItem(m_symbols);
0128                 }
0129                 // add an entry for the class
0130                 node->setText(0, identifier);
0131                 node->setIcon(0, m_icon_class);
0132                 node->setText(1, QString::number(i, 10));
0133                 if (m_expandOn->isChecked()) {
0134                     m_symbols->expandItem(node);
0135                 }
0136             } // (look for classes)
0137 
0138             // look for function definitions
0139             if ((current == QLatin1Char('f')) && (stripped.indexOf(QLatin1String("function "), c) == c)) {
0140                 function_start = c;
0141                 c += 8;
0142                 // look for the beginning of the parameters
0143                 identifier.clear();
0144                 for (/*c = c*/; c < stripped.length(); c++) {
0145                     current = stripped.at(c);
0146                     // look for the beginning of the function definition
0147                     if ((current == QLatin1Char('(')) || (current == QLatin1Char('{'))) {
0148                         c--;
0149                         break;
0150                     } else {
0151                         identifier += current;
0152                     }
0153                 }
0154                 // trim off whitespace
0155                 identifier = identifier.trimmed();
0156                 // if we have an anonymous function, back up to see if it's assigned to anything
0157                 if (!(identifier.length() > 0)) {
0158                     QChar ch = QLatin1Char('\0');
0159                     for (int end = function_start - 1; end >= 0; end--) {
0160                         ch = stripped.at(end);
0161                         // skip whitespace
0162                         if ((ch == QLatin1Char(' ')) || (ch == QLatin1Char('\t'))) {
0163                             continue;
0164                         }
0165                         // if we hit an assignment or object property operator,
0166                         //  get the preceding identifier
0167                         if ((ch == QLatin1Char('=')) || (ch == QLatin1Char(':'))) {
0168                             end--;
0169                             while (end >= 0) {
0170                                 ch = stripped.at(end);
0171                                 if ((ch != QLatin1Char(' ')) && (ch != QLatin1Char('\t'))) {
0172                                     break;
0173                                 }
0174                                 end--;
0175                             }
0176                             int start = end;
0177                             while (start >= 0) {
0178                                 ch = stripped.at(start);
0179                                 if (((ch >= QLatin1Char('a')) && (ch <= QLatin1Char('z'))) || ((ch >= QLatin1Char('A')) && (ch <= QLatin1Char('Z')))
0180                                     || ((ch >= QLatin1Char('0')) && (ch <= QLatin1Char('9'))) || (ch == QLatin1Char('_'))) {
0181                                     start--;
0182                                 } else {
0183                                     start++;
0184                                     break;
0185                                 }
0186                             }
0187                             identifier = stripped.mid(start, (end - start) + 1);
0188                             break;
0189                         }
0190                         // if we hit something else, we're not going to be able
0191                         //  to read an assignment identifier
0192                         else {
0193                             break;
0194                         }
0195                     }
0196                 }
0197                 // if we have a function identifier, make a node
0198                 if (identifier.length() > 0) {
0199                     // make a node for the function
0200                     QTreeWidgetItem *parent = nullptr;
0201                     if (!nodes.isEmpty()) {
0202                         parent = nodes.last();
0203                     }
0204                     if ((m_treeOn->isChecked()) && (parent != nullptr)) {
0205                         node = new QTreeWidgetItem(parent);
0206                     } else {
0207                         node = new QTreeWidgetItem(m_symbols);
0208                     }
0209                     // mark the parent as a class (if it's not the root level)
0210                     if (parent != nullptr) {
0211                         parent->setIcon(0, m_icon_class);
0212                         // mark this function as a method of the parent
0213                         node->setIcon(0, m_icon_function);
0214                     }
0215                     // mark root-level functions as classes
0216                     else {
0217                         node->setIcon(0, m_icon_class);
0218                     }
0219                     // add the function
0220                     node->setText(0, identifier);
0221                     node->setText(1, QString::number(i, 10));
0222                     if (m_expandOn->isChecked()) {
0223                         m_symbols->expandItem(node);
0224                     }
0225                 }
0226             } // (look for functions)
0227 
0228             // look for QML id: ....
0229             if (QStringView(stripped).mid(c, 3) == QLatin1String("id:")) {
0230                 c += 3;
0231                 identifier.clear();
0232                 // parse the id name
0233                 for (/*c = c*/; c < stripped.length(); c++) {
0234                     current = stripped.at(c);
0235                     // look for the beginning of the id
0236                     if (current == QLatin1Char(';')) {
0237                         c--;
0238                         break;
0239                     } else {
0240                         identifier += current;
0241                     }
0242                 }
0243 
0244                 identifier = identifier.trimmed();
0245 
0246                 // if we have an id, make a node
0247                 if (identifier.length() > 0) {
0248                     QTreeWidgetItem *parent = nullptr;
0249                     if (!nodes.isEmpty()) {
0250                         parent = nodes.last();
0251                     }
0252                     if ((m_treeOn->isChecked()) && (parent != nullptr)) {
0253                         node = new QTreeWidgetItem(parent);
0254                     } else {
0255                         node = new QTreeWidgetItem(m_symbols);
0256                     }
0257 
0258                     // mark the node as a class
0259                     node->setIcon(0, m_icon_class);
0260 
0261                     // add the id
0262                     node->setText(0, identifier);
0263                     node->setText(1, QString::number(i, 10));
0264                     if (m_expandOn->isChecked()) {
0265                         m_symbols->expandItem(node);
0266                     }
0267                 }
0268             }
0269 
0270             // keep track of brace depth
0271             if (current == QLatin1Char('{')) {
0272                 // if a node has been added at this level or above,
0273                 //  use it to extend the stack
0274                 if (node != nullptr) {
0275                     nodes.append(node);
0276                     // if no node has been added, extend the last node to this depth
0277                 } else if (!nodes.isEmpty()) {
0278                     nodes.append(nodes.last());
0279                 }
0280             } else if (current == QLatin1Char('}')) {
0281                 // pop the last node off the stack
0282                 node = nullptr;
0283                 if (!nodes.isEmpty()) {
0284                     nodes.removeLast();
0285                 }
0286             }
0287         } // (scan the stripped line)
0288 
0289     } // (iterate through lines of the document)
0290 }