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"
7
8#include <QtCore/qdebug.h>
9#include <QtCore/qdir.h>
10#include <QtCore/qfile.h>
11#include <QtCore/qregularexpression.h>
12#include <QtCore/qtextstream.h>
13
14#include <cstdio>
15#include <cstdlib>
16
17using namespace Qt::Literals::StringLiterals;
18
19QT_BEGIN_NAMESPACE
20
21int Location::s_tabSize;
22int Location::s_warningCount = 0;
23int Location::s_warningLimit = -1;
24QString Location::s_programName;
25QString Location::s_project;
26QString Location::s_projectRoot;
27QSet<QString> Location::s_reports;
28QRegularExpression *Location::s_spuriousRegExp = nullptr;
29std::unique_ptr<QFile> Location::s_warningLogFile;
30std::unique_ptr<QTextStream> Location::s_warningLogStream;
31
32/*!
33 \class Location
34
35 \brief The Location class provides a way to mark a location in a file.
36
37 It maintains a stack of file positions. A file position
38 consists of the file path, line number, and column number.
39 The location is used for printing error messages that are
40 tied to a location in a file.
41 */
42
43/*!
44 Constructs an empty location.
45 */
46Location::Location() : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
47{
48 // nothing.
49}
50
51/*!
52 Constructs a location with (fileName, 1, 1) on its file
53 position stack.
54 */
55Location::Location(const QString &fileName)
56 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
57{
58 push(fileName);
59}
60
61/*!
62 The copy constructor copies the contents of \a other into
63 this Location using the assignment operator.
64 */
66 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
67{
68 *this = other;
69}
70
71/*!
72 The assignment operator does a deep copy of the entire
73 state of \a other into this Location.
74 */
76{
77 if (this == &other)
78 return *this;
79
80 QStack<StackEntry> *oldStk = m_stk;
81
82 m_stkBottom = other.m_stkBottom;
83 if (other.m_stk == nullptr) {
84 m_stk = nullptr;
85 m_stkTop = &m_stkBottom;
86 } else {
87 m_stk = new QStack<StackEntry>(*other.m_stk);
88 m_stkTop = &m_stk->top();
89 }
90 m_stkDepth = other.m_stkDepth;
91 m_etc = other.m_etc;
92 delete oldStk;
93 return *this;
94}
95
96/*!
97 Returns \c true if this instance points to the
98 same location as \a other.
99
100 Two locations are considered equal if both the
101 filePath() and lineNo() are equal, regardless of
102 stack depth.
103 */
104bool Location::operator==(const Location &other) const
105{
106 if (&other == this)
107 return true;
108
109 if (lineNo() != other.lineNo())
110 return false;
111
112 if (filePath() != other.filePath())
113 return false;
114
115 return true;
116}
117
118/*!
119 Returns \c true if this instance does not point to the
120 same location as \a other.
121 */
122bool Location::operator!=(const Location &other) const
123{
124 return !(*this == other);
125}
126
127/*!
128 If the file position on top of the stack has a line number
129 less than 1, set its line number to 1 and its column number
130 to 1. Otherwise, do nothing.
131 */
133{
134 if (m_stkTop->m_lineNo < 1) {
135 m_stkTop->m_lineNo = 1;
136 m_stkTop->m_columnNo = 1;
137 }
138}
139
140/*!
141 Advance the current file position, using \a ch to decide how to do
142 that. If \a ch is a \c{'\\n'}, increment the current line number and
143 set the column number to 1. If \ch is a \c{'\\t'}, increment to the
144 next tab column. Otherwise, increment the column number by 1.
145
146 The current file position is the one on top of the position stack.
147 */
148void Location::advance(QChar ch)
149{
150 if (ch == QLatin1Char('\n')) {
151 m_stkTop->m_lineNo++;
152 m_stkTop->m_columnNo = 1;
153 } else if (ch == QLatin1Char('\t')) {
154 m_stkTop->m_columnNo = 1 + s_tabSize * (m_stkTop->m_columnNo + s_tabSize - 1) / s_tabSize;
155 } else {
156 m_stkTop->m_columnNo++;
157 }
158}
159
160/*!
161 Pushes \a filePath onto the file position stack. The current
162 file position becomes (\a filePath, 1, 1).
163
164 \sa pop()
165*/
166void Location::push(const QString &filePath)
167{
168 if (m_stkDepth++ >= 1) {
169 if (m_stk == nullptr)
170 m_stk = new QStack<StackEntry>;
171 m_stk->push(StackEntry());
172 m_stkTop = &m_stk->top();
173 }
174
175 m_stkTop->m_filePath = filePath;
176 m_stkTop->m_lineNo = INT_MIN;
177 m_stkTop->m_columnNo = 1;
178}
179
180/*!
181 Pops the top of the internal stack. The current file position
182 becomes the next one in the new top of stack.
183
184 \sa push()
185*/
187{
188 if (--m_stkDepth == 0) {
189 m_stkBottom = StackEntry();
190 } else {
191 if (!m_stk)
192 return;
193 m_stk->pop();
194 if (m_stk->isEmpty()) {
195 delete m_stk;
196 m_stk = nullptr;
197 m_stkTop = &m_stkBottom;
198 } else {
199 m_stkTop = &m_stk->top();
200 }
201 }
202}
203
204/*! \fn bool Location::isEmpty() const
205
206 Returns \c true if there is no file name set yet; returns \c false
207 otherwise. The functions filePath(), lineNo() and columnNo()
208 must not be called on an empty Location object.
209 */
210
211/*! \fn const QString &Location::filePath() const
212 Returns the current path and file name. If the Location is
213 empty, the returned string is null.
214
215 \sa lineNo(), columnNo()
216 */
217
218/*!
219 Returns the file name part of the file path, ie the current
220 file. Returns an empty string if the file path is empty.
221 */
223{
224 QFileInfo fi(filePath());
225 return fi.fileName();
226}
227
228/*!
229 Returns the suffix of the file name. Returns an empty string
230 if the file path is empty.
231 */
233{
234 QString fp = filePath();
235 return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('.') + 1));
236}
237
238/*! \fn int Location::lineNo() const
239 Returns the current line number.
240 Must not be called on an empty Location object.
241
242 \sa filePath(), columnNo()
243*/
244
245/*! \fn int Location::columnNo() const
246 Returns the current column number.
247 Must not be called on an empty Location object.
248
249 \sa filePath(), lineNo()
250*/
251
252/*!
253 Writes \a message and \a details to stderr as a formatted
254 warning message. Does not write the message if qdoc is in
255 the Prepare phase.
256 */
257void Location::warning(const QString &message, const QString &details) const
258{
259 const auto &config = Config::instance();
260 if (!config.preparing() || config.singleExec())
261 emitMessage(Warning, message, details);
262}
263
264/*!
265 Writes \a message and \a details to stderr as a formatted
266 error message. Does not write the message if qdoc is in
267 the Prepare phase.
268 */
269void Location::error(const QString &message, const QString &details) const
270{
271 const auto &config = Config::instance();
272 if (!config.preparing() || config.singleExec())
273 emitMessage(Error, message, details);
274}
275
276/*!
277 Returns the error code QDoc should exit with; EXIT_SUCCESS
278 or the number of documentation warnings if they exceeded
279 the limit set by warninglimit configuration variable.
280 */
282{
283 if (s_warningLimit < 0 || s_warningCount <= s_warningLimit)
284 return EXIT_SUCCESS;
285
286 Location().emitMessage(
287 Error,
288 QStringLiteral("Documentation warnings (%1) exceeded the limit (%2) for '%3'.")
289 .arg(QString::number(s_warningCount), QString::number(s_warningLimit),
290 s_project),
291 QString());
292 return s_warningCount;
293}
294
295/*!
296 Writes \a message and \a details to stderr as a formatted
297 error message and then exits the program. qdoc prints fatal
298 errors in either phase (Prepare or Generate).
299 */
300void Location::fatal(const QString &message, const QString &details) const
301{
302 emitMessage(Error, message, details);
303 information(message);
304 information(details);
305 information("Aborting");
306 exit(EXIT_FAILURE);
307}
308
309/*!
310 Writes \a message and \a details to stderr as a formatted
311 report message.
312
313 A report does not include any filename/line number information.
314 Recurring reports with an identical \a message are ignored.
315
316 A report is generated only in \e generate or \e {single-exec}
317 phase. In \e {prepare} phase, this function does nothing.
318 */
319void Location::report(const QString &message, const QString &details) const
320{
321 const auto &config = Config::instance();
322 if ((!config.preparing() || config.singleExec()) && !s_reports.contains(message)) {
323 emitMessage(Report, message, details);
324 s_reports << message;
325 }
326}
327
328/*!
329 Gets several parameters from the config, including
330 tab size, program name, and a regular expression that
331 appears to be used for matching certain error messages
332 so that emitMessage() can avoid printing them.
333 */
335{
336 Config &config = Config::instance();
337 s_tabSize = config.get(CONFIG_TABSIZE).asInt();
338 s_programName = config.programName();
339 s_project = config.get(CONFIG_PROJECT).asString();
340
341 // Initialize project root for relative path calculation with priority:
342 // 1. QDOC_PROJECT_ROOT environment variable (highest priority)
343 // 2. projectroot configuration variable (fallback)
344 // 3. Leave empty if neither available (absolute paths)
345 QString qdocProjectRoot = qEnvironmentVariable("QDOC_PROJECT_ROOT");
346 if (qdocProjectRoot.isNull())
347 qdocProjectRoot = config.get(CONFIG_PROJECTROOT).asString();
348
349 if (!qdocProjectRoot.isEmpty() && QDir(qdocProjectRoot).exists())
350 s_projectRoot = QDir::cleanPath(qdocProjectRoot);
351
352 if (!config.singleExec())
353 s_warningCount = 0;
354 if (qEnvironmentVariableIsSet("QDOC_ENABLE_WARNINGLIMIT")
355 || config.get(CONFIG_WARNINGLIMIT + Config::dot + "enabled").asBool())
356 s_warningLimit = config.get(CONFIG_WARNINGLIMIT).asInt();
357
358 QRegularExpression regExp = config.getRegExp(CONFIG_SPURIOUS);
359 if (regExp.isValid()) {
360 s_spuriousRegExp = new QRegularExpression(regExp);
361 } else {
362 config.get(CONFIG_SPURIOUS).location()
363 .warning(QStringLiteral("Invalid regular expression '%1'")
364 .arg(regExp.pattern()));
365 }
366
367 if (config.get(CONFIG_LOGWARNINGS).asBool())
368 initializeWarningLog(config);
369}
370
371/*!
372 Creates the header for the warning log file.
373*/
374QString Location::warningLogHeader()
375{
376 const auto &config = Config::instance();
377 QStringList lines;
378
379 lines << "# QDoc Warning Log"_L1;
380 lines << "# Project: "_L1 + s_project;
381
382 // Add command line arguments unless disabled
383 if (!config.get(CONFIG_LOGWARNINGSDISABLECLIARGS).asBool()) {
384 const QStringList args = QCoreApplication::arguments();
385 if (!args.isEmpty()) {
386 QStringList quotedArgs;
387 for (const QString &arg : args) {
388 // Quote arguments containing spaces
389 if (arg.contains(QLatin1Char(' '))) {
390 quotedArgs << '"'_L1 + arg + '"'_L1;
391 } else {
392 quotedArgs << arg;
393 }
394 }
395 lines << "# Command: "_L1 + quotedArgs.join(QLatin1Char(' '));
396 }
397 }
398
399 if (!s_projectRoot.isEmpty()) {
400 // Indicate which method was used to determine project root
401 if (!qEnvironmentVariable("QDOC_PROJECT_ROOT").isNull()) {
402 lines << "# Root: QDOC_PROJECT_ROOT"_L1;
403 } else {
404 lines << "# Root: projectroot config"_L1;
405 }
406 lines << "# Path-Format: relative"_L1;
407 } else {
408 lines << "# Path-Format: absolute"_L1;
409 }
410
411 lines << "#"_L1;
412 return lines.join('\n'_L1);
413}
414
415/*!
416 Formats a file path for the warning log, converting to relative path
417 if a project root is configured.
418 */
419QString Location::formatPathForWarningLog(const QString &path)
420{
421 if (s_projectRoot.isEmpty())
422 return path;
423
424 QDir projectDir(s_projectRoot);
425 QString relativePath = projectDir.relativeFilePath(path);
426
427 // Only use relative path if it doesn't go outside the project root
428 if (!relativePath.startsWith("../"_L1))
429 return relativePath;
430
431 return path;
432}
433
434/*!
435 Initializes the warning log file, creates the output directory if needed, and
436 adds the command-line arguments to QDoc to the top of the log file.
437
438 This function assumes that log warnings are enabled and should only be
439 called when \c {CONFIG_LOGWARNINGS} is \c {true}.
440 */
441void Location::initializeWarningLog(const Config &config)
442{
443 const QString logFileName = s_project + "-qdoc-warnings.log";
444 const QString &outputDir = config.getOutputDir();
445
446 QDir dir(outputDir);
447 if (!dir.exists()) {
448 dir.mkpath(".");
449 }
450
451 const QString &logFilePath = dir.filePath(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 = 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:452
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:222
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:300
QString fileSuffix() const
Returns the suffix of the file name.
Definition location.cpp:232
bool operator==(const Location &other) const
Returns true if this instance points to the same location as other.
Definition location.cpp:104
Location(const Location &other)
The copy constructor copies the contents of other into this Location using the assignment operator.
Definition location.cpp:65
void error(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted error message.
Definition location.cpp:269
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:281
void report(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted report message.
Definition location.cpp:319
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:122
static void initialize()
Gets several parameters from the config, including tab size, program name, and a regular expression t...
Definition location.cpp:334
Location()
Constructs an empty location.
Definition location.cpp:46
void push(const QString &filePath)
Pushes filePath onto the file position stack.
Definition location.cpp:166
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:132
void warning(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted warning message.
Definition location.cpp:257
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:148
Location & operator=(const Location &other)
The assignment operator does a deep copy of the entire state of other into this Location.
Definition location.cpp:75
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:186
Location(const QString &filePath)
Constructs a location with (fileName, 1, 1) on its file position stack.
Definition location.cpp:55
#define CONFIG_TABSIZE
Definition config.h:435
#define CONFIG_LOGWARNINGSDISABLECLIARGS
Definition config.h:407
#define CONFIG_WARNINGLIMIT
Definition config.h:450
#define CONFIG_LOGWARNINGS
Definition config.h:406
#define CONFIG_PROJECT
Definition config.h:419
#define CONFIG_PROJECTROOT
Definition config.h:420
#define CONFIG_SPURIOUS
Definition config.h:432