| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "location.h" |
| |
| #include "config.h" |
| #include "generator.h" |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qdir.h> |
| #include <QtCore/qregexp.h> |
| #include <QtCore/QTime> |
| |
| #include <limits.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| int Location::tabSize; |
| int Location::warningCount = 0; |
| int Location::warningLimit = -1; |
| QString Location::programName; |
| QString Location::project; |
| QRegExp *Location::spuriousRegExp = nullptr; |
| |
| /*! |
| \class Location |
| |
| \brief The Location class provides a way to mark a location in a file. |
| |
| It maintains a stack of file positions. A file position |
| consists of the file path, line number, and column number. |
| The location is used for printing error messages that are |
| tied to a location in a file. |
| */ |
| |
| /*! |
| Constructs an empty location. |
| */ |
| Location::Location() : stk(nullptr), stkTop(&stkBottom), stkDepth(0), etcetera(false) |
| { |
| // nothing. |
| } |
| |
| /*! |
| Constructs a location with (fileName, 1, 1) on its file |
| position stack. |
| */ |
| Location::Location(const QString &fileName) |
| : stk(nullptr), stkTop(&stkBottom), stkDepth(0), etcetera(false) |
| { |
| push(fileName); |
| } |
| |
| /*! |
| The copy constructor copies the contents of \a other into |
| this Location using the assignment operator. |
| */ |
| Location::Location(const Location &other) |
| : stk(nullptr), stkTop(&stkBottom), stkDepth(0), etcetera(false) |
| { |
| *this = other; |
| } |
| |
| /*! |
| The assignment operator does a deep copy of the entire |
| state of \a other into this Location. |
| */ |
| Location &Location::operator=(const Location &other) |
| { |
| QStack<StackEntry> *oldStk = stk; |
| |
| stkBottom = other.stkBottom; |
| if (other.stk == nullptr) { |
| stk = nullptr; |
| stkTop = &stkBottom; |
| } else { |
| stk = new QStack<StackEntry>(*other.stk); |
| stkTop = &stk->top(); |
| } |
| stkDepth = other.stkDepth; |
| etcetera = other.etcetera; |
| delete oldStk; |
| return *this; |
| } |
| |
| /*! |
| If the file position on top of the stack has a line number |
| less than 1, set its line number to 1 and its column number |
| to 1. Otherwise, do nothing. |
| */ |
| void Location::start() |
| { |
| if (stkTop->lineNo < 1) { |
| stkTop->lineNo = 1; |
| stkTop->columnNo = 1; |
| } |
| } |
| |
| /*! |
| Advance the current file position, using \a ch to decide how to do |
| that. If \a ch is a \c{'\\n'}, increment the current line number and |
| set the column number to 1. If \ch is a \c{'\\t'}, increment to the |
| next tab column. Otherwise, increment the column number by 1. |
| |
| The current file position is the one on top of the position stack. |
| */ |
| void Location::advance(QChar ch) |
| { |
| if (ch == QLatin1Char('\n')) { |
| stkTop->lineNo++; |
| stkTop->columnNo = 1; |
| } else if (ch == QLatin1Char('\t')) { |
| stkTop->columnNo = 1 + tabSize * (stkTop->columnNo + tabSize - 1) / tabSize; |
| } else { |
| stkTop->columnNo++; |
| } |
| } |
| |
| /*! |
| Pushes \a filePath onto the file position stack. The current |
| file position becomes (\a filePath, 1, 1). |
| |
| \sa pop() |
| */ |
| void Location::push(const QString &filePath) |
| { |
| if (stkDepth++ >= 1) { |
| if (stk == nullptr) |
| stk = new QStack<StackEntry>; |
| stk->push(StackEntry()); |
| stkTop = &stk->top(); |
| } |
| |
| stkTop->filePath = filePath; |
| stkTop->lineNo = INT_MIN; |
| stkTop->columnNo = 1; |
| } |
| |
| /*! |
| Pops the top of the internal stack. The current file position |
| becomes the next one in the new top of stack. |
| |
| \sa push() |
| */ |
| void Location::pop() |
| { |
| if (--stkDepth == 0) { |
| stkBottom = StackEntry(); |
| } else { |
| stk->pop(); |
| if (stk->isEmpty()) { |
| delete stk; |
| stk = nullptr; |
| stkTop = &stkBottom; |
| } else { |
| stkTop = &stk->top(); |
| } |
| } |
| } |
| |
| /*! \fn bool Location::isEmpty() const |
| |
| Returns \c true if there is no file name set yet; returns \c false |
| otherwise. The functions filePath(), lineNo() and columnNo() |
| must not be called on an empty Location object. |
| */ |
| |
| /*! \fn const QString &Location::filePath() const |
| Returns the current path and file name. If the Location is |
| empty, the returned string is null. |
| |
| \sa lineNo(), columnNo() |
| */ |
| |
| /*! |
| Returns the file name part of the file path, ie the current |
| file. Returns an empty string if the file path is empty. |
| */ |
| QString Location::fileName() const |
| { |
| QFileInfo fi(filePath()); |
| return fi.fileName(); |
| } |
| |
| /*! |
| Returns the suffix of the file name. Returns an empty string |
| if the file path is empty. |
| */ |
| QString Location::fileSuffix() const |
| { |
| QString fp = filePath(); |
| return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('.') + 1)); |
| } |
| |
| /*! |
| \brief Returns \a path which is canonicalized and relative to the config file. |
| |
| QDir::relativeFilePath does not canonicalize the paths, so |
| if the config file is located at qtbase\src\widgets\doc\qtwidgets.qdocconf |
| and it has a reference to any ancestor folder (e.g. ".." or even "../doc") |
| */ |
| QString Location::canonicalRelativePath(const QString &path) |
| { |
| QDir configFileDir(QDir::current()); |
| QDir dir(path); |
| const QString canon = dir.canonicalPath(); |
| return configFileDir.relativeFilePath(canon); |
| } |
| |
| /*! \fn int Location::lineNo() const |
| Returns the current line number. |
| Must not be called on an empty Location object. |
| |
| \sa filePath(), columnNo() |
| */ |
| |
| /*! \fn int Location::columnNo() const |
| Returns the current column number. |
| Must not be called on an empty Location object. |
| |
| \sa filePath(), lineNo() |
| */ |
| |
| /*! |
| Writes \a message and \a detals to stderr as a formatted |
| warning message. Does not write the message if qdoc is in |
| the Prepare phase. |
| */ |
| void Location::warning(const QString &message, const QString &details) const |
| { |
| const auto &config = Config::instance(); |
| if (!config.preparing() || config.singleExec()) |
| emitMessage(Warning, message, details); |
| } |
| |
| /*! |
| Writes \a message and \a detals to stderr as a formatted |
| error message. Does not write the message if qdoc is in |
| the Prepare phase. |
| */ |
| void Location::error(const QString &message, const QString &details) const |
| { |
| const auto &config = Config::instance(); |
| if (!config.preparing() || config.singleExec()) |
| emitMessage(Error, message, details); |
| } |
| |
| /*! |
| Returns the error code QDoc should exit with; EXIT_SUCCESS |
| or the number of documentation warnings if they exceeded |
| the limit set by warninglimit configuration variable. |
| */ |
| int Location::exitCode() |
| { |
| if (warningLimit < 0 || warningCount <= warningLimit) |
| return EXIT_SUCCESS; |
| |
| Location().emitMessage( |
| Error, |
| tr("Documentation warnings (%1) exceeded the limit (%2) for '%3'.") |
| .arg(QString::number(warningCount), QString::number(warningLimit), project), |
| QString()); |
| return warningCount; |
| } |
| |
| /*! |
| Writes \a message and \a detals to stderr as a formatted |
| error message and then exits the program. qdoc prints fatal |
| errors in either phase (Prepare or Generate). |
| */ |
| void Location::fatal(const QString &message, const QString &details) const |
| { |
| emitMessage(Error, message, details); |
| information(message); |
| information(details); |
| information("Aborting"); |
| exit(EXIT_FAILURE); |
| } |
| |
| /*! |
| Writes \a message and \a detals to stderr as a formatted |
| report message. |
| */ |
| void Location::report(const QString &message, const QString &details) const |
| { |
| emitMessage(Report, message, details); |
| } |
| |
| /*! |
| Gets several parameters from the config, including |
| tab size, program name, and a regular expression that |
| appears to be used for matching certain error messages |
| so that emitMessage() can avoid printing them. |
| */ |
| void Location::initialize() |
| { |
| Config &config = Config::instance(); |
| tabSize = config.getInt(CONFIG_TABSIZE); |
| programName = config.programName(); |
| project = config.getString(CONFIG_PROJECT); |
| warningCount = 0; |
| if (qEnvironmentVariableIsSet("QDOC_ENABLE_WARNINGLIMIT") |
| || config.getBool(CONFIG_WARNINGLIMIT + Config::dot + "enabled")) |
| warningLimit = config.getInt(CONFIG_WARNINGLIMIT); |
| |
| QRegExp regExp = config.getRegExp(CONFIG_SPURIOUS); |
| if (regExp.isValid()) { |
| spuriousRegExp = new QRegExp(regExp); |
| } else { |
| config.lastLocation().warning(tr("Invalid regular expression '%1'").arg(regExp.pattern())); |
| } |
| } |
| |
| /*! |
| Apparently, all this does is delete the regular expression |
| used for intercepting certain error messages that should |
| not be emitted by emitMessage(). |
| */ |
| void Location::terminate() |
| { |
| delete spuriousRegExp; |
| spuriousRegExp = nullptr; |
| } |
| |
| /*! |
| Prints \a message to \c stdout followed by a \c{'\n'}. |
| */ |
| void Location::information(const QString &message) |
| { |
| printf("%s\n", message.toLatin1().data()); |
| fflush(stdout); |
| } |
| |
| /*! |
| Report a program bug, including the \a hint. |
| */ |
| void Location::internalError(const QString &hint) |
| { |
| Location().fatal(tr("Internal error (%1)").arg(hint), |
| tr("There is a bug in %1. Seek advice from your local" |
| " %2 guru.") |
| .arg(programName) |
| .arg(programName)); |
| } |
| |
| /*! |
| Formats \a message and \a details into a single string |
| and outputs that string to \c stderr. \a type specifies |
| whether the \a message is an error or a warning. |
| */ |
| void Location::emitMessage(MessageType type, const QString &message, const QString &details) const |
| { |
| if (type == Warning && spuriousRegExp != nullptr && spuriousRegExp->exactMatch(message)) |
| return; |
| |
| QString result = message; |
| if (!details.isEmpty()) |
| result += "\n[" + details + QLatin1Char(']'); |
| result.replace("\n", "\n "); |
| if (isEmpty()) { |
| if (type == Error) |
| result.prepend(tr(": error: ")); |
| else if (type == Warning) { |
| result.prepend(tr(": warning: ")); |
| ++warningCount; |
| } |
| } else { |
| if (type == Error) |
| result.prepend(tr(": (qdoc) error: ")); |
| else if (type == Warning) { |
| result.prepend(tr(": (qdoc) warning: ")); |
| ++warningCount; |
| } |
| } |
| if (type != Report) |
| result.prepend(toString()); |
| fprintf(stderr, "%s\n", result.toLatin1().data()); |
| fflush(stderr); |
| } |
| |
| /*! |
| Converts the location to a string to be prepended to error |
| messages. |
| */ |
| QString Location::toString() const |
| { |
| QString str; |
| |
| if (isEmpty()) { |
| str = programName; |
| } else { |
| Location loc2 = *this; |
| loc2.setEtc(false); |
| loc2.pop(); |
| if (!loc2.isEmpty()) { |
| QString blah = tr("In file included from "); |
| for (;;) { |
| str += blah; |
| str += loc2.top(); |
| loc2.pop(); |
| if (loc2.isEmpty()) |
| break; |
| str += tr(","); |
| str += QLatin1Char('\n'); |
| blah.fill(' '); |
| } |
| str += tr(":"); |
| str += QLatin1Char('\n'); |
| } |
| str += top(); |
| } |
| return str; |
| } |
| |
| QString Location::top() const |
| { |
| QDir path(filePath()); |
| QString str = path.absolutePath(); |
| if (lineNo() >= 1) { |
| str += QLatin1Char(':'); |
| str += QString::number(lineNo()); |
| } |
| if (etc()) |
| str += QLatin1String(" (etc.)"); |
| return str; |
| } |
| |
| QT_END_NAMESPACE |