Revision control
++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qqmldomformatdirectivescanner_p.h"
++ #include "qqmldomoutwriter_p.h"
++ #include "qqmldomlinewriter_p.h"
++ #include "qqmldomindentinglinewriter_p.h"
++ #include "qqmldomitem_p.h"
++ #include "qqmldomcomments_p.h"
++
++ #include <QtCore/QLoggingCategory>
++
++ QT_BEGIN_NAMESPACE
++ namespace QQmlJS {
++ namespace Dom {
++
++ using DisabledRegionIt = OutWriter::OffsetToDisabledRegionMap::const_iterator;
++
++ static inline OutWriter::RegionToCommentMap extractComments(const DomItem &it)
++ {
++ OutWriter::RegionToCommentMap comments;
++ DomItem commentsField = it.field(Fields::comments);
++ if (const RegionComments *cRegionsPtr = commentsField.as<RegionComments>()) {
++ comments = cRegionsPtr->regionComments();
++ }
++ return comments;
++ }
++
++ /*
++ \internal
++ \brief Utility function to determine if two source locations overlap
++ */
++ static inline bool overlaps(const SourceLocation &a, const SourceLocation &b)
++ {
++ return a.isValid() && b.isValid() && (a.begin() < b.end() && b.begin() < a.end());
++ }
++
++ /*
++ \internal
++ \brief Utility function to determine the indent after skipping to format a piece of code
++ */
++ static inline int indentAfterPartialFormatting(int initialIndent, QStringView code,
++ LineWriterOptions options)
++ {
++ FormatTextStatus initialState = FormatTextStatus::initialStatus(initialIndent);
++ FormatPartialStatus partialStatus({}, options.formatOptions, initialState);
++ IndentingLineWriter indentingLineWriter([](QStringView line) { Q_UNUSED(line) }, QString(),
++ options, partialStatus.currentStatus);
++ OutWriter indentTracker(indentingLineWriter);
++ const auto commentLines = code.split(u'\n');
++ for (const auto &line : commentLines) {
++ if (!line.isEmpty()) {
++ partialStatus =
++ formatCodeLine(line, options.formatOptions, partialStatus.currentStatus);
++ indentTracker.write(line);
++ }
++ }
++
++ return indentTracker.indent;
++ }
++
++ /*
++ \internal
++ \brief Utility function to determine if a given location overlapping disabled region, returns an
++ iterator to the region if found, or end() if not found
++ */
++ static inline DisabledRegionIt
++ findOverlappingRegion(const SourceLocation &loc,
++ const OutWriter::OffsetToDisabledRegionMap &formatDisabledRegions)
++ {
++ if (!loc.isValid())
++ return formatDisabledRegions.cend();
++
++ return std::find_if(formatDisabledRegions.cbegin(), formatDisabledRegions.cend(),
++ [&loc](const auto &it) { return it.isValid() && overlaps(loc, it); });
++ }
++
++ QStringView OutWriter::attachedDisableCode(quint32 offset) const
++ {
++ if (formatDisabledRegions.contains(offset)) {
++ const auto &loc = formatDisabledRegions.value(offset);
++ return code.mid(loc.offset, loc.length);
++ }
++ return {};
++ }
++
++ // This function examines the provided SourceLocation to determine if it overlaps with any regions
++ // where formatting is disabled. If such a region is found and the formatter is currently enabled,
++ // it writes the disabled region and disables the formatter. If no overlapping region is found,
++ // the formatter is enabled.
++ void OutWriter::maybeWriteDisabledRegion(const SourceLocation &loc)
++ {
++ if (!loc.isValid())
++ return;
++ if (formatDisabledRegions.isEmpty())
++ return;
++ if (const auto foundRegionIt = findOverlappingRegion(loc, formatDisabledRegions);
++ foundRegionIt != formatDisabledRegions.end()) {
++ if (isFormatterEnabled) {
++ writeDisabledRegion(loc);
++ isFormatterEnabled = false;
++ }
++ } else {
++ isFormatterEnabled = true;
++ }
++ }
++
++ // Decides whether the given region should be formatted or not, based on the
++ // disabled regions found in the file and updates and returns the formatting enabled state.
++ bool OutWriter::shouldFormat(const FileLocations::Tree &fLoc, FileLocationRegion region)
++ {
++ if (!fLoc || formatDisabledRegions.isEmpty())
++ return isFormatterEnabled;
++
++ if (const auto regions = fLoc->info().regions; regions.contains(region)) {
++ isFormatterEnabled = findOverlappingRegion(regions.value(region), formatDisabledRegions)
++ == formatDisabledRegions.end();
++ }
++ return isFormatterEnabled;
++ }
++
++ void OutWriter::scanFormatDirectives(QStringView code, const QList<SourceLocation> &comments)
++ {
++ // Disabled regions cannot be effective if the line writer options
++ // are set to normalize or sort imports.
++ const auto shouldScanDirectives = lineWriter.options().attributesSequence
++ != LineWriterOptions::AttributesSequence::Normalize
++ && !lineWriter.options().sortImports;
++ if (!shouldScanDirectives)
++ return;
++
++ formatDisabledRegions = QmlFormat::identifyDisabledRegions(code, comments);
++ }
++
++ bool OutWriter::formatterEnabled() const
++ {
++ return isFormatterEnabled;
++ }
++
++ void OutWriter::writeDisabledRegion(const SourceLocation &loc)
++ {
++ const auto disabledCode = attachedDisableCode(loc.offset);
++ int newIndent = indentAfterPartialFormatting(indent, disabledCode, lineWriter.options());
++ lineWriter.ensureNewline();
++ lineWriter.setLineIndent(0);
++ indentNextlines = false;
++ lineWriter.write(disabledCode);
++ lineWriter.setLineIndent(newIndent);
++ indentNextlines = true;
++ }
++
++ void OutWriter::maybeWriteComment(const Comment &comment)
++ {
++ maybeWriteDisabledRegion(comment.sourceLocation());
++
++ if (!skipComments && formatterEnabled()) {
++ comment.write(*this);
++ }
++
++ // if disabled, maybe reenabled with this comment
++ if (!formatterEnabled()) {
++ auto directive = QmlFormat::directiveFromComment(comment.rawComment());
++ if (directive == QmlFormat::Directive::On)
++ isFormatterEnabled = true;
++ }
++ }
++
++ void OutWriter::itemStart(const DomItem &it)
++ {
++ if (skipComments)
++ return;
++
++ pendingComments.push(extractComments(it));
++ writePreComment(MainRegion);
++ }
++
++ void OutWriter::itemEnd()
++ {
++ if (skipComments)
++ return;
++
++ Q_ASSERT(!pendingComments.isEmpty());
++ writePostComment(MainRegion);
++ pendingComments.pop();
++ }
++
++ void OutWriter::writePreComment(FileLocationRegion region)
++ {
++ if (skipComments)
++ return;
++
++ const auto &comments = pendingComments.top();
++ if (comments.contains(region)) {
++ const auto attachedComments = comments[region];
++ for (const auto &comment : attachedComments.preComments())
++ maybeWriteComment(comment);
++ }
++ }
++
++ void OutWriter::writePostComment(FileLocationRegion region)
++ {
++ if (skipComments)
++ return;
++
++ auto &comments = pendingComments.top();
++ if (comments.contains(region)) {
++ const auto attachedComments = comments[region];
++ for (const auto &comment : attachedComments.postComments())
++ maybeWriteComment(comment);
++ comments.remove(region);
++ }
++ }
++
++ static bool regionIncreasesIndentation(FileLocationRegion region)
++ {
++ switch (region) {
++ case LeftBraceRegion:
++ return true;
++ case LeftBracketRegion:
++ return true;
++ default:
++ return false;
++ }
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ static bool regionDecreasesIndentation(FileLocationRegion region)
++ {
++ switch (region) {
++ case RightBraceRegion:
++ return true;
++ case RightBracketRegion:
++ return true;
++ default:
++ return false;
++ }
++ Q_UNREACHABLE_RETURN(false);
++ }
++
++ /*!
++ \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.
++ */
++ // TODO(QTBUG-138020)
++ OutWriter &OutWriter::writeRegion(const FileLocations::Tree &fLoc, FileLocationRegion region)
++ {
++ using namespace Qt::Literals::StringLiterals;
++ 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;
++ case YieldKeywordRegion:
++ codeForRegion = u"yield"_s;
++ break;
++ case NewKeywordRegion:
++ codeForRegion = u"new"_s;
++ break;
++ case ThisKeywordRegion:
++ codeForRegion = u"this"_s;
++ break;
++ case SuperKeywordRegion:
++ codeForRegion = u"super"_s;
++ break;
++ case StarTokenRegion:
++ codeForRegion = u"*"_s;
++ break;
++ case DollarLeftBraceTokenRegion:
++ codeForRegion = u"${"_s;
++ break;
++ case LeftBacktickTokenRegion:
++ case RightBacktickTokenRegion:
++ codeForRegion = u"`"_s;
++ break;
++ case VirtualKeywordRegion:
++ codeForRegion = u"virtual"_s;
++ break;
++ case OverrideKeywordRegion:
++ codeForRegion = u"override"_s;
++ break;
++ case FinalKeywordRegion:
++ codeForRegion = u"final"_s;
++ break;
++ // not keywords:
++ case ImportUriRegion:
++ case IdNameRegion:
++ case IdentifierRegion:
++ case PragmaValuesRegion:
++ case MainRegion:
++ case OnTargetRegion:
++ case TypeIdentifierRegion:
++ case TypeModifierRegion:
++ 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(fLoc, region, codeForRegion);
++ }
++
++ OutWriter &OutWriter::writeRegion(const FileLocations::Tree &fLoc, FileLocationRegion region,
++ QStringView toWrite)
++ {
++ writePreComment(region);
++ if (regionDecreasesIndentation(region))
++ decreaseIndent(1);
++ if (shouldFormat(fLoc, region))
++ lineWriter.write(toWrite);
++ if (regionIncreasesIndentation(region))
++ increaseIndent(1);
++ writePostComment(region);
++ return *this;
++ }
++
++ } // namespace Dom
++ } // namespace QQmlJS
++ QT_END_NAMESPACE