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