Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
location.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "location.h"
5
6#include "config.h"
8
9#include <QtCore/qdebug.h>
10#include <QtCore/qdir.h>
11#include <QtCore/qfile.h>
12#include <QtCore/qregularexpression.h>
13#include <QtCore/qtextstream.h>
14
15#include <cstdio>
16#include <cstdlib>
17#include <utility>
18
19using namespace Qt::Literals::StringLiterals;
20
21QT_BEGIN_NAMESPACE
22
23int Location::s_tabSize;
24int Location::s_warningCount = 0;
25int Location::s_warningLimit = -1;
26QString Location::s_programName;
27QString Location::s_project;
28QString Location::s_projectRoot;
29QSet<QString> Location::s_reports;
30QRegularExpression *Location::s_spuriousRegExp = nullptr;
31std::unique_ptr<QFile> Location::s_warningLogFile;
32std::unique_ptr<QTextStream> Location::s_warningLogStream;
33
34/*!
35 \class Location
36
37 \brief The Location class provides a way to mark a location in a file.
38
39 It maintains a stack of file positions. A file position
40 consists of the file path, line number, and column number.
41 The location is used for printing error messages that are
42 tied to a location in a file.
43 */
44
45/*!
46 Constructs an empty location.
47 */
48Location::Location() : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
49{
50 // nothing.
51}
52
53/*!
54 Constructs a location with (fileName, 1, 1) on its file
55 position stack.
56 */
57Location::Location(const QString &fileName)
58 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
59{
60 push(fileName);
61}
62
63/*!
64 The copy constructor copies the contents of \a other into
65 this Location using the assignment operator.
66 */
68 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
69{
70 *this = other;
71}
72
73/*!
74 The assignment operator does a deep copy of the entire
75 state of \a other into this Location.
76 */
78{
79 if (this == &other)
80 return *this;
81
82 QStack<StackEntry> *oldStk = m_stk;
83
84 m_stkBottom = other.m_stkBottom;
85 if (other.m_stk == nullptr) {
86 m_stk = nullptr;
87 m_stkTop = &m_stkBottom;
88 } else {
89 m_stk = new QStack<StackEntry>(*other.m_stk);
90 m_stkTop = &m_stk->top();
91 }
92 m_stkDepth = other.m_stkDepth;
93 m_etc = other.m_etc;
94 delete oldStk;
95 return *this;
96}
97
98/*!
99 Returns \c true if this instance points to the
100 same location as \a other.
101
102 Two locations are considered equal if both the
103 filePath() and lineNo() are equal, regardless of
104 stack depth.
105 */
106bool Location::operator==(const Location &other) const
107{
108 if (&other == this)
109 return true;
110
111 if (lineNo() != other.lineNo())
112 return false;
113
114 if (filePath() != other.filePath())
115 return false;
116
117 return true;
118}
119
120/*!
121 Returns \c true if this instance does not point to the
122 same location as \a other.
123 */
124bool Location::operator!=(const Location &other) const
125{
126 return !(*this == other);
127}
128
129/*!
130 If the file position on top of the stack has a line number
131 less than 1, set its line number to 1 and its column number
132 to 1. Otherwise, do nothing.
133 */
135{
136 if (m_stkTop->m_lineNo < 1) {
137 m_stkTop->m_lineNo = 1;
138 m_stkTop->m_columnNo = 1;
139 }
140}
141
142/*!
143 Advance the current file position, using \a ch to decide how to do
144 that. If \a ch is a \c{'\\n'}, increment the current line number and
145 set the column number to 1. If \ch is a \c{'\\t'}, increment to the
146 next tab column. Otherwise, increment the column number by 1.
147
148 The current file position is the one on top of the position stack.
149 */
150void Location::advance(QChar ch)
151{
152 if (ch == QLatin1Char('\n')) {
153 m_stkTop->m_lineNo++;
154 m_stkTop->m_columnNo = 1;
155 } else if (ch == QLatin1Char('\t')) {
156 m_stkTop->m_columnNo = 1 + s_tabSize * (m_stkTop->m_columnNo + s_tabSize - 1) / s_tabSize;
157 } else {
158 m_stkTop->m_columnNo++;
159 }
160}
161
162/*!
163 Pushes \a filePath onto the file position stack. The current
164 file position becomes (\a filePath, 1, 1).
165
166 \sa pop()
167*/
168void Location::push(const QString &filePath)
169{
170 if (m_stkDepth++ >= 1) {
171 if (m_stk == nullptr)
172 m_stk = new QStack<StackEntry>;
173 m_stk->push(StackEntry());
174 m_stkTop = &m_stk->top();
175 }
176
177 m_stkTop->m_filePath = filePath;
178 m_stkTop->m_lineNo = INT_MIN;
179 m_stkTop->m_columnNo = 1;
180}
181
182/*!
183 Pops the top of the internal stack. The current file position
184 becomes the next one in the new top of stack.
185
186 \sa push()
187*/
189{
190 if (--m_stkDepth == 0) {
191 m_stkBottom = StackEntry();
192 } else {
193 if (!m_stk)
194 return;
195 m_stk->pop();
196 if (m_stk->isEmpty()) {
197 delete m_stk;
198 m_stk = nullptr;
199 m_stkTop = &m_stkBottom;
200 } else {
201 m_stkTop = &m_stk->top();
202 }
203 }
204}
205
206/*! \fn bool Location::isEmpty() const
207
208 Returns \c true if there is no file name set yet; returns \c false
209 otherwise. The functions filePath(), lineNo() and columnNo()
210 must not be called on an empty Location object.
211 */
212
213/*! \fn const QString &Location::filePath() const
214 Returns the current path and file name. If the Location is
215 empty, the returned string is null.
216
217 \sa lineNo(), columnNo()
218 */
219
220/*!
221 Returns the file name part of the file path, ie the current
222 file. Returns an empty string if the file path is empty.
223 */
225{
226 QFileInfo fi(filePath());
227 return fi.fileName();
228}
229
230/*!
231 Returns the suffix of the file name. Returns an empty string
232 if the file path is empty.
233 */
235{
236 QString fp = filePath();
237 return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('.') + 1));
238}
239
240/*! \fn int Location::lineNo() const
241 Returns the current line number.
242 Must not be called on an empty Location object.
243
244 \sa filePath(), columnNo()
245*/
246
247/*! \fn int Location::columnNo() const
248 Returns the current column number.
249 Must not be called on an empty Location object.
250
251 \sa filePath(), lineNo()
252*/
253
254/*!
255 Writes \a message and \a details to stderr as a formatted
256 warning message. Does not write the message if qdoc is in
257 the Prepare phase.
258 */
259void Location::warning(const QString &message, const QString &details) const
260{
261 const auto &config = Config::instance();
262 if (!config.preparing() || config.singleExec())
263 emitMessage(Warning, message, details);
264}
265
266/*!
267 Writes \a message and \a details to stderr as a formatted
268 error message. Does not write the message if qdoc is in
269 the Prepare phase.
270 */
271void Location::error(const QString &message, const QString &details) const
272{
273 const auto &config = Config::instance();
274 if (!config.preparing() || config.singleExec())
275 emitMessage(Error, message, details);
276}
277
278/*!
279 Returns the error code QDoc should exit with; EXIT_SUCCESS
280 or the number of documentation warnings if they exceeded
281 the limit set by warninglimit configuration variable.
282 */
284{
285 if (s_warningLimit < 0 || s_warningCount <= s_warningLimit)
286 return EXIT_SUCCESS;
287
288 Location().emitMessage(
289 Error,
290 QStringLiteral("Documentation warnings (%1) exceeded the limit (%2) for '%3'.")
291 .arg(QString::number(s_warningCount), QString::number(s_warningLimit),
292 s_project),
293 QString());
294 return s_warningCount;
295}
296
297/*!
298 Writes \a message and \a details to stderr as a formatted
299 error message and then exits the program. qdoc prints fatal
300 errors in either phase (Prepare or Generate).
301 */
302void Location::fatal(const QString &message, const QString &details) const
303{
304 emitMessage(Error, message, details);
305 information(message);
306 information(details);
307 information("Aborting");
308 exit(EXIT_FAILURE);
309}
310
311/*!
312 Writes \a message and \a details to stderr as a formatted
313 report message.
314
315 A report does not include any filename/line number information.
316 Recurring reports with an identical \a message are ignored.
317
318 A report is generated only in \e generate or \e {single-exec}
319 phase. In \e {prepare} phase, this function does nothing.
320 */
321void Location::report(const QString &message, const QString &details) const
322{
323 const auto &config = Config::instance();
324 if ((!config.preparing() || config.singleExec()) && !s_reports.contains(message)) {
325 emitMessage(Report, message, details);
326 s_reports << message;
327 }
328}
329
330/*!
331 Gets several parameters from the config, including
332 tab size, program name, and a regular expression that
333 appears to be used for matching certain error messages
334 so that emitMessage() can avoid printing them.
335 */
337{
338 Config &config = Config::instance();
339 s_tabSize = config.get(CONFIG_TABSIZE).asInt();
340 s_programName = config.programName();
341 s_project = config.get(CONFIG_PROJECT).asString();
342
343 // Initialize project root for relative path calculation with priority:
344 // 1. QDOC_PROJECT_ROOT environment variable (highest priority)
345 // 2. projectroot configuration variable (fallback)
346 // 3. Leave empty if neither available (absolute paths)
347 QString qdocProjectRoot = qEnvironmentVariable("QDOC_PROJECT_ROOT");
348 if (qdocProjectRoot.isNull())
349 qdocProjectRoot = config.get(CONFIG_PROJECTROOT).asString();
350
351 if (!qdocProjectRoot.isEmpty() && QDir(qdocProjectRoot).exists())
352 s_projectRoot = QDir::cleanPath(qdocProjectRoot);
353
354 if (!config.singleExec())
355 s_warningCount = 0;
356 if (qEnvironmentVariableIsSet("QDOC_ENABLE_WARNINGLIMIT")
357 || config.get(CONFIG_WARNINGLIMIT + Config::dot + "enabled").asBool())
358 s_warningLimit = config.get(CONFIG_WARNINGLIMIT).asInt();
359
360 QRegularExpression regExp = config.getRegExp(CONFIG_SPURIOUS);
361 if (regExp.isValid()) {
362 s_spuriousRegExp = new QRegularExpression(regExp);
363 } else {
364 config.get(CONFIG_SPURIOUS).location()
365 .warning(QStringLiteral("Invalid regular expression '%1'")
366 .arg(regExp.pattern()));
367 }
368
369 if (config.get(CONFIG_LOGWARNINGS).asBool())
370 initializeWarningLog(config);
371}
372
373/*!
374 Creates the header for the warning log file.
375*/
376QString Location::warningLogHeader()
377{
378 const auto &config = Config::instance();
379 QStringList lines;
380
381 lines << "# QDoc Warning Log"_L1;
382 lines << "# Project: "_L1 + s_project;
383
384 // Add command line arguments unless disabled
385 if (!config.get(CONFIG_LOGWARNINGSDISABLECLIARGS).asBool()) {
386 const QStringList args = QCoreApplication::arguments();
387 if (!args.isEmpty()) {
388 QStringList quotedArgs;
389 for (const QString &arg : args) {
390 // Quote arguments containing spaces
391 if (arg.contains(QLatin1Char(' '))) {
392 quotedArgs << '"'_L1 + arg + '"'_L1;
393 } else {
394 quotedArgs << arg;
395 }
396 }
397 lines << "# Command: "_L1 + quotedArgs.join(QLatin1Char(' '));
398 }
399 }
400
401 if (!s_projectRoot.isEmpty()) {
402 // Indicate which method was used to determine project root
403 if (!qEnvironmentVariable("QDOC_PROJECT_ROOT").isNull()) {
404 lines << "# Root: QDOC_PROJECT_ROOT"_L1;
405 } else {
406 lines << "# Root: projectroot config"_L1;
407 }
408 lines << "# Path-Format: relative"_L1;
409 } else {
410 lines << "# Path-Format: absolute"_L1;
411 }
412
413 lines << "#"_L1;
414 return lines.join('\n'_L1);
415}
416
417/*!
418 Formats a file path for the warning log, converting to relative path
419 if a project root is configured.
420 */
421QString Location::formatPathForWarningLog(const QString &path)
422{
423 if (s_projectRoot.isEmpty())
424 return path;
425
426 QDir projectDir(s_projectRoot);
427 QString relativePath = projectDir.relativeFilePath(path);
428
429 // Only use relative path if it doesn't go outside the project root
430 if (!relativePath.startsWith("../"_L1))
431 return relativePath;
432
433 return path;
434}
435
436/*!
437 Initializes the warning log file, creates the output directory if needed, and
438 adds the command-line arguments to QDoc to the top of the log file.
439
440 This function assumes that log warnings are enabled and should only be
441 called when \c {CONFIG_LOGWARNINGS} is \c {true}.
442 */
443void Location::initializeWarningLog(const Config &config)
444{
445 const QString logFileName = s_project + "-qdoc-warnings.log";
446 const QString &outputDir = config.getOutputDir();
447
448 const OutputDirectory dir =
449 OutputDirectory::ensure(outputDir, Location());
450
451 const QString &logFilePath = dir.absoluteFilePath(logFileName);
452
453 s_warningLogFile = std::make_unique<QFile>(logFilePath);
454 if (s_warningLogFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
455 s_warningLogStream = std::make_unique<QTextStream>(s_warningLogFile.get());
456
457 *s_warningLogStream << warningLogHeader() << Qt::endl;
458 *s_warningLogStream << Qt::endl;
459 } else {
460 Location().warning(QStringLiteral("Failed to open warning log file: %1").arg(logFilePath));
461 s_warningLogFile.reset();
462 }
463}
464
465/*!
466 Writes the message \a formattedMessage to the warning log file if it is
467 enabled and the message type \a type is a warning.
468 */
469void Location::writeToWarningLog(MessageType type, const QString &formattedMessage)
470{
471 if (type != Warning || !s_warningLogStream)
472 return;
473
474 *s_warningLogStream << formattedMessage << Qt::endl;
475}
476
477/*!
478 Apparently, all this does is delete the regular expression
479 used for intercepting certain error messages that should
480 not be emitted by emitMessage().
481 */
483{
484 delete s_spuriousRegExp;
485 s_spuriousRegExp = nullptr;
486
487 s_warningLogStream.reset();
488 s_warningLogFile.reset();
489}
490
491/*!
492 Prints \a message to \c stdout followed by a \c{'\n'}.
493 */
494void Location::information(const QString &message)
495{
496 printf("%s\n", message.toLatin1().data());
497 fflush(stdout);
498}
499
500/*!
501 Report a program bug, including the \a hint.
502 */
503void Location::internalError(const QString &hint)
504{
505 Location().fatal(QStringLiteral("Internal error (%1)").arg(hint),
506 QStringLiteral("There is a bug in %1. Seek advice from your local"
507 " %2 guru.")
508 .arg(s_programName, s_programName));
509}
510
511/*!
512 Formats \a message and \a details into a single string
513 and outputs that string to \c stderr. \a type specifies
514 whether the \a message is an error or a warning.
515 */
516void Location::emitMessage(MessageType type, const QString &message, const QString &details) const
517{
518 if (type == Warning && s_spuriousRegExp != nullptr) {
519 auto match = s_spuriousRegExp->match(message, 0, QRegularExpression::NormalMatch,
520 QRegularExpression::AnchorAtOffsetMatchOption);
521 if (match.hasMatch() && match.capturedLength() == message.size())
522 return;
523 }
524
525 QString result = message;
526 if (!details.isEmpty())
527 result += "\n[" + details + QLatin1Char(']');
528 result.replace("\n", "\n ");
529 if (isEmpty()) {
530 if (type == Error)
531 result.prepend(QStringLiteral(": error: "));
532 else if (type == Warning) {
533 result.prepend(QStringLiteral(": warning: "));
534 ++s_warningCount;
535 }
536 } else {
537 if (type == Error)
538 result.prepend(": [%1] (qdoc) error: "_L1.arg(s_project));
539 else if (type == Warning) {
540 result.prepend(": [%1] (qdoc) warning: "_L1.arg(s_project));
541 ++s_warningCount;
542 }
543 }
544 if (type != Report)
545 result.prepend(toString());
546 else
547 result.prepend("qdoc: '%1': "_L1.arg(s_project));
548 fprintf(stderr, "%s\n", result.toLatin1().data());
549 fflush(stderr);
550
551 // Create a version with relative paths for the warning log
552 QString logMessage = std::move(result);
553 if (type == Warning && !s_projectRoot.isEmpty()) {
554 // Replace the absolute path portion with a relative path for log file
555 QString locationString = toString();
556 QString formattedLocationString = formatPathForWarningLog(locationString);
557 logMessage.replace(locationString, formattedLocationString);
558 }
559 writeToWarningLog(type, logMessage);
560}
561
562/*!
563 Converts the location to a string to be prepended to error
564 messages.
565 */
567{
568 QString str;
569
570 if (isEmpty()) {
571 str = s_programName;
572 } else {
573 Location loc2 = *this;
574 loc2.setEtc(false);
575 loc2.pop();
576 if (!loc2.isEmpty()) {
577 QString blah = QStringLiteral("In file included from ");
578 for (;;) {
579 str += blah;
580 str += loc2.top();
581 loc2.pop();
582 if (loc2.isEmpty())
583 break;
584 str += QStringLiteral(",\n");
585 blah.fill(' ');
586 }
587 str += QStringLiteral(":\n");
588 }
589 str += top();
590 }
591 return str;
592}
593
594QString Location::top() const
595{
596 QDir path(filePath());
597 QString str = path.absolutePath();
598 if (lineNo() >= 1) {
599 str += QLatin1Char(':');
600 str += QString::number(lineNo());
601 }
602 if (etc())
603 str += QLatin1String(" (etc.)");
604 return str;
605}
606
607QT_END_NAMESPACE
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:85
bool singleExec() const
Definition config.h:457
The Location class provides a way to mark a location in a file.
Definition location.h:20
QString fileName() const
Returns the file name part of the file path, ie the current file.
Definition location.cpp:224
void fatal(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted error message and then exits the program.
Definition location.cpp:302
QString fileSuffix() const
Returns the suffix of the file name.
Definition location.cpp:234
bool operator==(const Location &other) const
Returns true if this instance points to the same location as other.
Definition location.cpp:106
Location(const Location &other)
The copy constructor copies the contents of other into this Location using the assignment operator.
Definition location.cpp:67
void error(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted error message.
Definition location.cpp:271
int lineNo() const
Returns the current line number.
Definition location.h:50
static int exitCode()
Returns the error code QDoc should exit with; EXIT_SUCCESS or the number of documentation warnings if...
Definition location.cpp:283
void report(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted report message.
Definition location.cpp:321
QString toString() const
Converts the location to a string to be prepended to error messages.
Definition location.cpp:566
bool operator!=(const Location &other) const
Returns true if this instance does not point to the same location as other.
Definition location.cpp:124
static void initialize()
Gets several parameters from the config, including tab size, program name, and a regular expression t...
Definition location.cpp:336
Location()
Constructs an empty location.
Definition location.cpp:48
void push(const QString &filePath)
Pushes filePath onto the file position stack.
Definition location.cpp:168
void start()
If the file position on top of the stack has a line number less than 1, set its line number to 1 and ...
Definition location.cpp:134
void warning(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted warning message.
Definition location.cpp:259
void setEtc(bool etc)
Definition location.h:41
void advance(QChar ch)
Advance the current file position, using ch to decide how to do that.
Definition location.cpp:150
Location & operator=(const Location &other)
The assignment operator does a deep copy of the entire state of other into this Location.
Definition location.cpp:77
bool isEmpty() const
Returns true if there is no file name set yet; returns false otherwise.
Definition location.h:45
bool etc() const
Definition location.h:52
static void terminate()
Apparently, all this does is delete the regular expression used for intercepting certain error messag...
Definition location.cpp:482
void pop()
Pops the top of the internal stack.
Definition location.cpp:188
Location(const QString &filePath)
Constructs a location with (fileName, 1, 1) on its file position stack.
Definition location.cpp:57
Represents an output directory that has been verified to exist.
#define CONFIG_TABSIZE
Definition config.h:440
#define CONFIG_LOGWARNINGSDISABLECLIARGS
Definition config.h:410
#define CONFIG_WARNINGLIMIT
Definition config.h:455
#define CONFIG_LOGWARNINGS
Definition config.h:409
#define CONFIG_PROJECT
Definition config.h:423
#define CONFIG_PROJECTROOT
Definition config.h:424
#define CONFIG_SPURIOUS
Definition config.h:437