diff options
Diffstat (limited to 'src/qmldom/qqmldomoutwriter.cpp')
-rw-r--r-- | src/qmldom/qqmldomoutwriter.cpp | 363 |
1 files changed, 264 insertions, 99 deletions
diff --git a/src/qmldom/qqmldomoutwriter.cpp b/src/qmldom/qqmldomoutwriter.cpp index 7bdbf505e2..7f4f219cbf 100644 --- a/src/qmldom/qqmldomoutwriter.cpp +++ b/src/qmldom/qqmldomoutwriter.cpp @@ -15,14 +15,13 @@ QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -OutWriterState::OutWriterState(Path itCanonicalPath, DomItem &it, FileLocations::Tree fLoc) +OutWriterState::OutWriterState( + const Path &itCanonicalPath, const DomItem &it, const FileLocations::Tree &fLoc) : itemCanonicalPath(itCanonicalPath), item(it), currentMap(fLoc) { DomItem cRegions = it.field(Fields::comments); - if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>()) { - pendingComments = cRegionsPtr->regionComments; - fLoc->info().ensureCommentLocations(pendingComments.keys()); - } + if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>()) + pendingComments = cRegionsPtr->regionComments(); } void OutWriterState::closeState(OutWriter &w) @@ -50,7 +49,7 @@ OutWriterState &OutWriter::state(int i) return states[states.size() - 1 - i]; } -void OutWriter::itemStart(DomItem &it) +void OutWriter::itemStart(const DomItem &it) { if (!topLocation->path()) topLocation->setPath(it.canonicalPath()); @@ -74,135 +73,301 @@ void OutWriter::itemStart(DomItem &it) if (updateLocs) state().fullRegionId = lineWriter.startSourceLocation( [newFLoc](SourceLocation l) { FileLocations::updateFullLocation(newFLoc, l); }); - regionStart(QString()); + regionStart(MainRegion); } -void OutWriter::itemEnd(DomItem &it) +void OutWriter::itemEnd(const DomItem &it) { Q_ASSERT(states.size() > 0); Q_ASSERT(state().item == it); - regionEnd(QString()); + regionEnd(MainRegion); state().closeState(*this); states.removeLast(); } -void OutWriter::regionStart(QString rName) +void OutWriter::regionStart(FileLocationRegion region) { - Q_ASSERT(!state().pendingRegions.contains(rName)); + Q_ASSERT(!state().pendingRegions.contains(region)); FileLocations::Tree fMap = state().currentMap; - if (!skipComments && state().pendingComments.contains(rName)) { + if (!skipComments && state().pendingComments.contains(region)) { bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations; QList<SourceLocation> *cLocs = - (updateLocs ? &(fMap->info().preCommentLocations[rName]) : nullptr); - state().pendingComments[rName].writePre(*this, cLocs); + (updateLocs ? &(fMap->info().preCommentLocations[region]) : nullptr); + state().pendingComments[region].writePre(*this, cLocs); } - state().pendingRegions[rName] = lineWriter.startSourceLocation( - [rName, fMap](SourceLocation l) { FileLocations::addRegion(fMap, rName, l); }); + state().pendingRegions[region] = lineWriter.startSourceLocation( + [region, fMap](SourceLocation l) { FileLocations::addRegion(fMap, region, l); }); } -void OutWriter::regionEnd(QString rName) +void OutWriter::regionEnd(FileLocationRegion region) { - Q_ASSERT(state().pendingRegions.contains(rName)); + Q_ASSERT(state().pendingRegions.contains(region)); FileLocations::Tree fMap = state().currentMap; - lineWriter.endSourceLocation(state().pendingRegions.value(rName)); - state().pendingRegions.remove(rName); - if (state().pendingComments.contains(rName)) { + lineWriter.endSourceLocation(state().pendingRegions.value(region)); + state().pendingRegions.remove(region); + if (state().pendingComments.contains(region)) { if (!skipComments) { bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations; QList<SourceLocation> *cLocs = - (updateLocs ? &(fMap->info().postCommentLocations[rName]) : nullptr); - state().pendingComments[rName].writePost(*this, cLocs); + (updateLocs ? &(fMap->info().postCommentLocations[region]) : nullptr); + state().pendingComments[region].writePost(*this, cLocs); } - state().pendingComments.remove(rName); + state().pendingComments.remove(region); } } -OutWriter &OutWriter::writeRegion(QString rName, QStringView toWrite) +/*! +\internal +Helper method for writeRegion(FileLocationRegion region) that allows to use +\c{writeRegion(ColonTokenRegion);} instead of having to write out the more error-prone +\c{writeRegion(ColonTokenRegion, ":");} for tokens and keywords. +*/ +OutWriter &OutWriter::writeRegion(FileLocationRegion region) { - regionStart(rName); + QString codeForRegion; + switch (region) { + case ComponentKeywordRegion: + codeForRegion = u"component"_s; + break; + case IdColonTokenRegion: + case ColonTokenRegion: + codeForRegion = u":"_s; + break; + case ImportTokenRegion: + codeForRegion = u"import"_s; + break; + case AsTokenRegion: + codeForRegion = u"as"_s; + break; + case OnTokenRegion: + codeForRegion = u"on"_s; + break; + case IdTokenRegion: + codeForRegion = u"id"_s; + break; + case LeftBraceRegion: + codeForRegion = u"{"_s; + break; + case RightBraceRegion: + codeForRegion = u"}"_s; + break; + case LeftBracketRegion: + codeForRegion = u"["_s; + break; + case RightBracketRegion: + codeForRegion = u"]"_s; + break; + case LeftParenthesisRegion: + codeForRegion = u"("_s; + break; + case RightParenthesisRegion: + codeForRegion = u")"_s; + break; + case EnumKeywordRegion: + codeForRegion = u"enum"_s; + break; + case DefaultKeywordRegion: + codeForRegion = u"default"_s; + break; + case RequiredKeywordRegion: + codeForRegion = u"required"_s; + break; + case ReadonlyKeywordRegion: + codeForRegion = u"readonly"_s; + break; + case PropertyKeywordRegion: + codeForRegion = u"property"_s; + break; + case FunctionKeywordRegion: + codeForRegion = u"function"_s; + break; + case SignalKeywordRegion: + codeForRegion = u"signal"_s; + break; + case ReturnKeywordRegion: + codeForRegion = u"return"_s; + break; + case EllipsisTokenRegion: + codeForRegion = u"..."_s; + break; + case EqualTokenRegion: + codeForRegion = u"="_s; + break; + case PragmaKeywordRegion: + codeForRegion = u"pragma"_s; + break; + case CommaTokenRegion: + codeForRegion = u","_s; + break; + case ForKeywordRegion: + codeForRegion = u"for"_s; + break; + case ElseKeywordRegion: + codeForRegion = u"else"_s; + break; + case DoKeywordRegion: + codeForRegion = u"do"_s; + break; + case WhileKeywordRegion: + codeForRegion = u"while"_s; + break; + case TryKeywordRegion: + codeForRegion = u"try"_s; + break; + case CatchKeywordRegion: + codeForRegion = u"catch"_s; + break; + case FinallyKeywordRegion: + codeForRegion = u"finally"_s; + break; + case CaseKeywordRegion: + codeForRegion = u"case"_s; + break; + case ThrowKeywordRegion: + codeForRegion = u"throw"_s; + break; + case ContinueKeywordRegion: + codeForRegion = u"continue"_s; + break; + case BreakKeywordRegion: + codeForRegion = u"break"_s; + break; + case QuestionMarkTokenRegion: + codeForRegion = u"?"_s; + break; + case SemicolonTokenRegion: + codeForRegion = u";"_s; + break; + case IfKeywordRegion: + codeForRegion = u"if"_s; + break; + case SwitchKeywordRegion: + codeForRegion = u"switch"_s; + break; + // not keywords: + case ImportUriRegion: + case IdNameRegion: + case IdentifierRegion: + case PragmaValuesRegion: + case MainRegion: + case OnTargetRegion: + case TypeIdentifierRegion: + case FirstSemicolonTokenRegion: + case SecondSemicolonRegion: + case InOfTokenRegion: + case OperatorTokenRegion: + case VersionRegion: + case EnumValueRegion: + Q_ASSERT_X(false, "regionToString", "Using regionToString on a value or an identifier!"); + return *this; + } + + return writeRegion(region, codeForRegion); +} + +OutWriter &OutWriter::writeRegion(FileLocationRegion region, QStringView toWrite) +{ + regionStart(region); lineWriter.write(toWrite); - regionEnd(rName); + regionEnd(region); return *this; } +/*! + \internal + Restores written out FileItem using intermediate information saved during DOM traversal. + It enables verifying DOM consistency of the written item later. -DomItem OutWriter::updatedFile(DomItem &qmlFile) + At the moment of writing, intermediate information consisting only of UpdatedScriptExpression, + however this is subject for change. The process of restoration is the following: + 1. Creating copy of the initial fileItem + 2. Updating relevant data/subitems modified during the WriteOut + 3. Returning an item containing updates. + */ +DomItem OutWriter::restoreWrittenFileItem(const DomItem &fileItem) { - Q_ASSERT(qmlFile.internalKind() == DomType::QmlFile); - if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) { - std::shared_ptr<QmlFile> copyPtr = qmlFilePtr->makeCopy(qmlFile); - DomItem env = qmlFile.environment(); - std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>(); - Q_ASSERT(envPtr); - auto newEnvPtr = std::make_shared<DomEnvironment>( - envPtr, envPtr->loadPaths(), envPtr->options()); - newEnvPtr->addQmlFile(copyPtr); - MutableDomItem copy = MutableDomItem(DomItem(newEnvPtr).copy(copyPtr)); - FileLocations::Tree newLoc = topLocation; - Path qmlFilePath = qmlFile.canonicalPath(); - if (newLoc->path() != qmlFilePath) { - if (newLoc->path()) { - if (newLoc->path().length() > qmlFilePath.length() - && newLoc->path().mid(0, qmlFilePath.length()) == qmlFilePath) { - newLoc = FileLocations::createTree(qmlFilePath); - FileLocations::Tree loc = - FileLocations::ensure(newLoc, newLoc->path().mid(qmlFilePath.length()), - AttachedInfo::PathType::Relative); - loc->setSubItems(topLocation->subItems()); - } else { - qCWarning(writeOutLog) - << "failed to base fileLocations in OutWriter (" << newLoc->path() - << ") to current file (" << qmlFilePath << ")"; - } - } else { - newLoc = FileLocations::createTree(qmlFilePath); - Q_ASSERT(newLoc->subItems().isEmpty() && newLoc->info().regions.isEmpty()); + switch (fileItem.internalKind()) { + case DomType::QmlFile: + return writtenQmlFileItem(fileItem, fileItem.canonicalPath()); + case DomType::JsFile: + return writtenJsFileItem(fileItem, fileItem.canonicalPath()); + default: + qCWarning(writeOutLog) << fileItem.internalKind() << " is not supported"; + return DomItem{}; + } +} + +DomItem OutWriter::writtenQmlFileItem(const DomItem &fileItem, const Path &filePath) +{ + Q_ASSERT(fileItem.internalKind() == DomType::QmlFile); + auto mutableFile = fileItem.makeCopy(DomItem::CopyOption::EnvDisconnected); + // QmlFile specific visitor for reformattedScriptExpressions tree + // lambda function responsible for the update of the initial expression by the formatted one + auto exprUpdater = [&mutableFile, filePath]( + const Path &p, const UpdatedScriptExpression::Tree &t) { + if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) { + Q_ASSERT(p.mid(0, filePath.length()) == filePath); + MutableDomItem originalExprItem = mutableFile.path(p.mid(filePath.length())); + if (!originalExprItem) + qCWarning(writeOutLog) << "failed to get" << p.mid(filePath.length()) << "from" + << mutableFile.canonicalPath(); + // Verifying originalExprItem.as<ScriptExpression>() == false is handy + // because we can't call setScript on the ScriptExpression itself and it needs to + // be called on the container / parent item. See setScript for details + else if (formattedExpr->ast() + || (!originalExprItem.as<ScriptExpression>() + || !originalExprItem.as<ScriptExpression>()->ast())) + originalExprItem.setScript(formattedExpr); + else { + logScriptExprUpdateSkipped(originalExprItem.item(), + originalExprItem.canonicalPath(), formattedExpr); } } - copyPtr->setFileLocationsTree(newLoc); - UpdatedScriptExpression::visitTree( - reformattedScriptExpressions, - [©, qmlFilePath](Path p, UpdatedScriptExpression::Tree t) { - if (std::shared_ptr<ScriptExpression> exprPtr = t->info().expr) { - Q_ASSERT(p.mid(0, qmlFilePath.length()) == qmlFilePath); - MutableDomItem targetExpr = copy.path(p.mid(qmlFilePath.length())); - if (!targetExpr) - qCWarning(writeOutLog) << "failed to get" << p.mid(qmlFilePath.length()) - << "from" << copy.canonicalPath(); - else if (exprPtr->ast() - || (!targetExpr.as<ScriptExpression>() - || !targetExpr.as<ScriptExpression>()->ast())) - targetExpr.setScript(exprPtr); - else { - qCWarning(writeOutLog).noquote() - << "Skipped update of reformatted ScriptExpression with " - "code:\n---------------\n" - << exprPtr->code() << "\n---------------\n preCode:" << - [exprPtr](Sink s) { sinkEscaped(s, exprPtr->preCode()); } - << "\n postCode: " << - [exprPtr](Sink s) { sinkEscaped(s, exprPtr->postCode()); } - << "\n as it failed standalone reparse with errors:" << - [&targetExpr, exprPtr](Sink s) { - targetExpr.item() - .copy(exprPtr, targetExpr.canonicalPath()) - .iterateErrors( - [s](DomItem, ErrorMessage msg) { - s(u"\n "); - msg.dump(s); - return true; - }, - true); - } - << "\n"; - } - } - return true; - }); - return copy.item(); - } - return DomItem(); + return true; + }; + // update relevant formatted expressions + UpdatedScriptExpression::visitTree(reformattedScriptExpressions, exprUpdater); + return mutableFile.item(); } +DomItem OutWriter::writtenJsFileItem(const DomItem &fileItem, const Path &filePath) +{ + Q_ASSERT(fileItem.internalKind() == DomType::JsFile); + auto mutableFile = fileItem.makeCopy(DomItem::CopyOption::EnvDisconnected); + UpdatedScriptExpression::visitTree( + reformattedScriptExpressions, + [&mutableFile, filePath](const Path &p, const UpdatedScriptExpression::Tree &t) { + if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) { + Q_ASSERT(p.mid(0, filePath.length()) == filePath); + mutableFile.mutableAs<JsFile>()->setExpression(formattedExpr); + } + return true; + }); + return mutableFile.item(); +} + +void OutWriter::logScriptExprUpdateSkipped( + const DomItem &exprItem, const Path &exprPath, + const std::shared_ptr<ScriptExpression> &formattedExpr) +{ + qCWarning(writeOutLog).noquote() << "Skipped update of reformatted ScriptExpression with " + "code:\n---------------\n" + << formattedExpr->code() << "\n---------------\n preCode:" << + [&formattedExpr](Sink s) { sinkEscaped(s, formattedExpr->preCode()); } + << "\n postCode: " << + [&formattedExpr](Sink s) { sinkEscaped(s, formattedExpr->postCode()); } + << "\n as it failed standalone reparse with errors:" << + [&exprItem, &exprPath, &formattedExpr](Sink s) { + exprItem.copy(formattedExpr, exprPath) + .iterateErrors( + [s](const DomItem &, const ErrorMessage &msg) { + s(u"\n "); + msg.dump(s); + return true; + }, + true); + } << "\n"; +} } // namespace Dom } // namespace QQmlJS QT_END_NAMESPACE |