File indexing completed on 2024-05-19 04:42:01
0001 /* 0002 SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "parsesession.h" 0008 #include "debugvisitor.h" 0009 #include "cache.h" 0010 0011 #include <qmljs/parser/qmljsast_p.h> 0012 #include <qmljs/parser/qmljsengine_p.h> 0013 0014 #include <language/duchain/stringhelpers.h> 0015 #include <language/duchain/duchain.h> 0016 #include <language/duchain/duchainlock.h> 0017 #include <language/duchain/declaration.h> 0018 #include <language/backgroundparser/backgroundparser.h> 0019 #include <language/editor/documentrange.h> 0020 #include <interfaces/ilanguagecontroller.h> 0021 #include <interfaces/icore.h> 0022 0023 using namespace KDevelop; 0024 0025 IndexedString ParseSession::languageString() 0026 { 0027 static const IndexedString langString("QML/JS"); 0028 return langString; 0029 } 0030 0031 bool isSorted(const QList<QmlJS::AST::SourceLocation>& locations) 0032 { 0033 if (locations.size() <= 1) { 0034 return true; 0035 } 0036 for(int i = 1; i < locations.size(); ++i) { 0037 if (locations.at(i).begin() <= locations.at(i-1).begin()) { 0038 return false; 0039 } 0040 } 0041 return true; 0042 } 0043 0044 QmlJS::Dialect ParseSession::guessLanguageFromSuffix(const QString& path) 0045 { 0046 if (path.endsWith(QLatin1String(".js"))) { 0047 return QmlJS::Dialect::JavaScript; 0048 } else if (path.endsWith(QLatin1String(".json"))) { 0049 return QmlJS::Dialect::Json; 0050 } else { 0051 return QmlJS::Dialect::Qml; 0052 } 0053 } 0054 0055 ParseSession::ParseSession(const IndexedString& url, const QString& contents, int priority) 0056 : m_url(url), 0057 m_ownPriority(priority), 0058 m_allDependenciesSatisfied(true) 0059 { 0060 const QString path = m_url.str(); 0061 m_doc = QmlJS::Document::create(path, guessLanguageFromSuffix(path)); 0062 m_doc->setSource(contents); 0063 m_doc->parse(); 0064 Q_ASSERT(isSorted(m_doc->engine()->comments())); 0065 0066 // Parse the module name and the version of url (this is used only when the file 0067 // is a QML module, but doesn't break for JavaScript files) 0068 m_baseName = QString::fromUtf8(m_url.byteArray()) 0069 .section(QLatin1Char('/'), -1, -1) // Base name 0070 .section(QLatin1Char('.'), 0, -2); // Without extension 0071 } 0072 0073 bool ParseSession::isParsedCorrectly() const 0074 { 0075 return m_doc->isParsedCorrectly(); 0076 } 0077 0078 QmlJS::AST::Node* ParseSession::ast() const 0079 { 0080 return m_doc->ast(); 0081 } 0082 0083 IndexedString ParseSession::url() const 0084 { 0085 return m_url; 0086 } 0087 0088 QString ParseSession::moduleName() const 0089 { 0090 return m_baseName; 0091 } 0092 0093 void ParseSession::addProblem(QmlJS::AST::Node* node, 0094 const QString& message, 0095 IProblem::Severity severity) 0096 { 0097 ProblemPointer p(new Problem); 0098 0099 p->setDescription(message); 0100 p->setSeverity(severity); 0101 p->setSource(IProblem::SemanticAnalysis); 0102 p->setFinalLocation(DocumentRange(m_url, editorFindRange(node, node).castToSimpleRange())); 0103 0104 m_problems << p; 0105 } 0106 0107 QList<ProblemPointer> ParseSession::problems() const 0108 { 0109 QList<ProblemPointer> problems = m_problems; 0110 0111 const auto diagnosticMessages = m_doc->diagnosticMessages(); 0112 problems.reserve(problems.size() + diagnosticMessages.size()); 0113 for (const auto& msg : diagnosticMessages) { 0114 ProblemPointer p(new Problem); 0115 p->setDescription(msg.message); 0116 p->setSeverity(IProblem::Error); 0117 p->setSource(IProblem::Parser); 0118 p->setFinalLocation(DocumentRange(m_url, locationToRange(msg.loc).castToSimpleRange())); 0119 problems << p; 0120 } 0121 0122 return problems; 0123 } 0124 0125 QString ParseSession::symbolAt(const QmlJS::AST::SourceLocation& location) const 0126 { 0127 return m_doc->source().mid(location.offset, location.length); 0128 } 0129 0130 QmlJS::Dialect ParseSession::language() const 0131 { 0132 return m_doc->language(); 0133 } 0134 0135 bool compareSourceLocation(const QmlJS::AST::SourceLocation& l, 0136 const QmlJS::AST::SourceLocation& r) 0137 { 0138 return l.begin() < r.begin(); 0139 } 0140 0141 QString ParseSession::commentForLocation(const QmlJS::AST::SourceLocation& location) const 0142 { 0143 // find most recent comment in sorted list of comments 0144 const QList< QmlJS::AST::SourceLocation >& comments = m_doc->engine()->comments(); 0145 auto it = std::lower_bound( 0146 comments.constBegin(), 0147 comments.constEnd(), 0148 location, compareSourceLocation 0149 ); 0150 0151 if (it == comments.constBegin()) { 0152 return QString(); 0153 } 0154 0155 // lower bound returns the place of insertion, 0156 // we want the comment before that 0157 it--; 0158 RangeInRevision input = locationToRange(location); 0159 RangeInRevision match = locationToRange(*it); 0160 if (match.end.line != input.start.line - 1 && match.end.line != input.start.line) { 0161 return QString(); 0162 } 0163 0164 ///TODO: merge consecutive //-style comments? 0165 return formatComment(symbolAt(*it)); 0166 } 0167 0168 RangeInRevision ParseSession::locationToRange(const QmlJS::AST::SourceLocation& location) const 0169 { 0170 const int linesInLocation = m_doc->source().midRef(location.offset, location.length).count(QLatin1Char('\n')); 0171 return RangeInRevision(location.startLine - 1, location.startColumn - 1, 0172 location.startLine - 1 + linesInLocation, location.startColumn - 1 + location.length); 0173 } 0174 0175 RangeInRevision ParseSession::locationsToRange(const QmlJS::AST::SourceLocation& locationFrom, 0176 const QmlJS::AST::SourceLocation& locationTo) const 0177 { 0178 return RangeInRevision(locationToRange(locationFrom).start, 0179 locationToRange(locationTo).end); 0180 } 0181 0182 RangeInRevision ParseSession::locationsToInnerRange(const QmlJS::AST::SourceLocation& locationFrom, 0183 const QmlJS::AST::SourceLocation& locationTo) const 0184 { 0185 return RangeInRevision(locationToRange(locationFrom).end, 0186 locationToRange(locationTo).start); 0187 } 0188 0189 RangeInRevision ParseSession::editorFindRange(QmlJS::AST::Node* fromNode, QmlJS::AST::Node* toNode) const 0190 { 0191 return locationsToRange(fromNode->firstSourceLocation(), toNode->lastSourceLocation()); 0192 } 0193 0194 void ParseSession::setContextOnNode(QmlJS::AST::Node* node, DUContext* context) 0195 { 0196 m_astToContext.insert(node, DUContextPointer(context)); 0197 } 0198 0199 DUContext* ParseSession::contextFromNode(QmlJS::AST::Node* node) const 0200 { 0201 return m_astToContext.value(node, DUContextPointer()).data(); 0202 } 0203 0204 bool ParseSession::allDependenciesSatisfied() const 0205 { 0206 return m_allDependenciesSatisfied; 0207 } 0208 0209 ReferencedTopDUContext ParseSession::contextOfFile(const QString& fileName) 0210 { 0211 ReferencedTopDUContext res = contextOfFile(fileName, m_url, m_ownPriority); 0212 0213 if (!res) { 0214 // The file was not yet present in the DUChain, store this information. 0215 // This will prevent the second parsing pass from running (it would be 0216 // useless as the file will be re-parsed when res will become available) 0217 m_allDependenciesSatisfied = false; 0218 } 0219 0220 return res; 0221 } 0222 0223 ReferencedTopDUContext ParseSession::contextOfFile(const QString& fileName, 0224 const KDevelop::IndexedString& url, 0225 int ownPriority) 0226 { 0227 if (fileName.isEmpty()) { 0228 return ReferencedTopDUContext(); 0229 } 0230 0231 // Get the top context of this module file 0232 DUChainReadLocker lock; 0233 IndexedString moduleFileString(fileName); 0234 ReferencedTopDUContext moduleContext = DUChain::self()->chainForDocument(moduleFileString); 0235 0236 lock.unlock(); 0237 QmlJS::Cache::instance().addDependency(url, moduleFileString); 0238 0239 if (!moduleContext) { 0240 // Queue the file on which we depend with a lower priority than the one of this file 0241 scheduleForParsing(moduleFileString, ownPriority - 1); 0242 0243 // Register a dependency between this file and the imported one 0244 return ReferencedTopDUContext(); 0245 } else { 0246 return moduleContext; 0247 } 0248 } 0249 0250 void ParseSession::reparseImporters() 0251 { 0252 const auto& files = QmlJS::Cache::instance().filesThatDependOn(m_url); 0253 for (const KDevelop::IndexedString& file : files) { 0254 scheduleForParsing(file, m_ownPriority); 0255 } 0256 } 0257 0258 void ParseSession::scheduleForParsing(const IndexedString& url, int priority) 0259 { 0260 BackgroundParser* bgparser = KDevelop::ICore::self()->languageController()->backgroundParser(); 0261 const auto features = TopDUContext::ForceUpdate | TopDUContext::AllDeclarationsContextsAndUses; 0262 0263 if (bgparser->isQueued(url)) { 0264 bgparser->removeDocument(url); 0265 } 0266 0267 bgparser->addDocument(url, features, priority, nullptr, ParseJob::FullSequentialProcessing); 0268 } 0269 0270 void ParseSession::dumpNode(QmlJS::AST::Node* node) const 0271 { 0272 DebugVisitor v(this); 0273 v.startVisiting(node); 0274 }