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/qregularexpression.h>
11
12#include <climits>
13#include <cstdio>
14#include <cstdlib>
15
16QT_BEGIN_NAMESPACE
17
18int Location::s_tabSize;
19int Location::s_warningCount = 0;
20int Location::s_warningLimit = -1;
21QString Location::s_programName;
22QString Location::s_project;
23QRegularExpression *Location::s_spuriousRegExp = nullptr;
24
25/*!
26 \class Location
27
28 \brief The Location class provides a way to mark a location in a file.
29
30 It maintains a stack of file positions. A file position
31 consists of the file path, line number, and column number.
32 The location is used for printing error messages that are
33 tied to a location in a file.
34 */
35
36/*!
37 Constructs an empty location.
38 */
39Location::Location() : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
40{
41 // nothing.
42}
43
44/*!
45 Constructs a location with (fileName, 1, 1) on its file
46 position stack.
47 */
48Location::Location(const QString &fileName)
49 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
50{
51 push(filePath: fileName);
52}
53
54/*!
55 The copy constructor copies the contents of \a other into
56 this Location using the assignment operator.
57 */
58Location::Location(const Location &other)
59 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
60{
61 *this = other;
62}
63
64/*!
65 The assignment operator does a deep copy of the entire
66 state of \a other into this Location.
67 */
68Location &Location::operator=(const Location &other)
69{
70 if (this == &other)
71 return *this;
72
73 QStack<StackEntry> *oldStk = m_stk;
74
75 m_stkBottom = other.m_stkBottom;
76 if (other.m_stk == nullptr) {
77 m_stk = nullptr;
78 m_stkTop = &m_stkBottom;
79 } else {
80 m_stk = new QStack<StackEntry>(*other.m_stk);
81 m_stkTop = &m_stk->top();
82 }
83 m_stkDepth = other.m_stkDepth;
84 m_etc = other.m_etc;
85 delete oldStk;
86 return *this;
87}
88
89/*!
90 If the file position on top of the stack has a line number
91 less than 1, set its line number to 1 and its column number
92 to 1. Otherwise, do nothing.
93 */
94void Location::start()
95{
96 if (m_stkTop->m_lineNo < 1) {
97 m_stkTop->m_lineNo = 1;
98 m_stkTop->m_columnNo = 1;
99 }
100}
101
102/*!
103 Advance the current file position, using \a ch to decide how to do
104 that. If \a ch is a \c{'\\n'}, increment the current line number and
105 set the column number to 1. If \ch is a \c{'\\t'}, increment to the
106 next tab column. Otherwise, increment the column number by 1.
107
108 The current file position is the one on top of the position stack.
109 */
110void Location::advance(QChar ch)
111{
112 if (ch == QLatin1Char('\n')) {
113 m_stkTop->m_lineNo++;
114 m_stkTop->m_columnNo = 1;
115 } else if (ch == QLatin1Char('\t')) {
116 m_stkTop->m_columnNo = 1 + s_tabSize * (m_stkTop->m_columnNo + s_tabSize - 1) / s_tabSize;
117 } else {
118 m_stkTop->m_columnNo++;
119 }
120}
121
122/*!
123 Pushes \a filePath onto the file position stack. The current
124 file position becomes (\a filePath, 1, 1).
125
126 \sa pop()
127*/
128void Location::push(const QString &filePath)
129{
130 if (m_stkDepth++ >= 1) {
131 if (m_stk == nullptr)
132 m_stk = new QStack<StackEntry>;
133 m_stk->push(t: StackEntry());
134 m_stkTop = &m_stk->top();
135 }
136
137 m_stkTop->m_filePath = filePath;
138 m_stkTop->m_lineNo = INT_MIN;
139 m_stkTop->m_columnNo = 1;
140}
141
142/*!
143 Pops the top of the internal stack. The current file position
144 becomes the next one in the new top of stack.
145
146 \sa push()
147*/
148void Location::pop()
149{
150 if (--m_stkDepth == 0) {
151 m_stkBottom = StackEntry();
152 } else {
153 if (!m_stk)
154 return;
155 m_stk->pop();
156 if (m_stk->isEmpty()) {
157 delete m_stk;
158 m_stk = nullptr;
159 m_stkTop = &m_stkBottom;
160 } else {
161 m_stkTop = &m_stk->top();
162 }
163 }
164}
165
166/*! \fn bool Location::isEmpty() const
167
168 Returns \c true if there is no file name set yet; returns \c false
169 otherwise. The functions filePath(), lineNo() and columnNo()
170 must not be called on an empty Location object.
171 */
172
173/*! \fn const QString &Location::filePath() const
174 Returns the current path and file name. If the Location is
175 empty, the returned string is null.
176
177 \sa lineNo(), columnNo()
178 */
179
180/*!
181 Returns the file name part of the file path, ie the current
182 file. Returns an empty string if the file path is empty.
183 */
184QString Location::fileName() const
185{
186 QFileInfo fi(filePath());
187 return fi.fileName();
188}
189
190/*!
191 Returns the suffix of the file name. Returns an empty string
192 if the file path is empty.
193 */
194QString Location::fileSuffix() const
195{
196 QString fp = filePath();
197 return (fp.isEmpty() ? fp : fp.mid(position: fp.lastIndexOf(c: '.') + 1));
198}
199
200/*! \fn int Location::lineNo() const
201 Returns the current line number.
202 Must not be called on an empty Location object.
203
204 \sa filePath(), columnNo()
205*/
206
207/*! \fn int Location::columnNo() const
208 Returns the current column number.
209 Must not be called on an empty Location object.
210
211 \sa filePath(), lineNo()
212*/
213
214/*!
215 Writes \a message and \a details to stderr as a formatted
216 warning message. Does not write the message if qdoc is in
217 the Prepare phase.
218 */
219void Location::warning(const QString &message, const QString &details) const
220{
221 const auto &config = Config::instance();
222 if (!config.preparing() || config.singleExec())
223 emitMessage(type: Warning, message, details);
224}
225
226/*!
227 Writes \a message and \a details to stderr as a formatted
228 error message. Does not write the message if qdoc is in
229 the Prepare phase.
230 */
231void Location::error(const QString &message, const QString &details) const
232{
233 const auto &config = Config::instance();
234 if (!config.preparing() || config.singleExec())
235 emitMessage(type: Error, message, details);
236}
237
238/*!
239 Returns the error code QDoc should exit with; EXIT_SUCCESS
240 or the number of documentation warnings if they exceeded
241 the limit set by warninglimit configuration variable.
242 */
243int Location::exitCode()
244{
245 if (s_warningLimit < 0 || s_warningCount <= s_warningLimit)
246 return EXIT_SUCCESS;
247
248 Location().emitMessage(
249 type: Error,
250 QStringLiteral("Documentation warnings (%1) exceeded the limit (%2) for '%3'.")
251 .arg(args: QString::number(s_warningCount), args: QString::number(s_warningLimit),
252 args&: s_project),
253 details: QString());
254 return s_warningCount;
255}
256
257/*!
258 Writes \a message and \a details to stderr as a formatted
259 error message and then exits the program. qdoc prints fatal
260 errors in either phase (Prepare or Generate).
261 */
262void Location::fatal(const QString &message, const QString &details) const
263{
264 emitMessage(type: Error, message, details);
265 information(message);
266 information(message: details);
267 information(message: "Aborting");
268 exit(EXIT_FAILURE);
269}
270
271/*!
272 Writes \a message and \a details to stderr as a formatted
273 report message.
274 */
275void Location::report(const QString &message, const QString &details) const
276{
277 emitMessage(type: Report, message, details);
278}
279
280/*!
281 Gets several parameters from the config, including
282 tab size, program name, and a regular expression that
283 appears to be used for matching certain error messages
284 so that emitMessage() can avoid printing them.
285 */
286void Location::initialize()
287{
288 Config &config = Config::instance();
289 s_tabSize = config.get(CONFIG_TABSIZE).asInt();
290 s_programName = config.programName();
291 s_project = config.get(CONFIG_PROJECT).asString();
292 if (!config.singleExec())
293 s_warningCount = 0;
294 if (qEnvironmentVariableIsSet(varName: "QDOC_ENABLE_WARNINGLIMIT")
295 || config.get(CONFIG_WARNINGLIMIT + Config::dot + "enabled").asBool())
296 s_warningLimit = config.get(CONFIG_WARNINGLIMIT).asInt();
297
298 QRegularExpression regExp = config.getRegExp(CONFIG_SPURIOUS);
299 if (regExp.isValid()) {
300 s_spuriousRegExp = new QRegularExpression(regExp);
301 } else {
302 config.get(CONFIG_SPURIOUS).location()
303 .warning(QStringLiteral("Invalid regular expression '%1'")
304 .arg(a: regExp.pattern()));
305 }
306}
307
308/*!
309 Apparently, all this does is delete the regular expression
310 used for intercepting certain error messages that should
311 not be emitted by emitMessage().
312 */
313void Location::terminate()
314{
315 delete s_spuriousRegExp;
316 s_spuriousRegExp = nullptr;
317}
318
319/*!
320 Prints \a message to \c stdout followed by a \c{'\n'}.
321 */
322void Location::information(const QString &message)
323{
324 printf(format: "%s\n", message.toLatin1().data());
325 fflush(stdout);
326}
327
328/*!
329 Report a program bug, including the \a hint.
330 */
331void Location::internalError(const QString &hint)
332{
333 Location().fatal(QStringLiteral("Internal error (%1)").arg(a: hint),
334 QStringLiteral("There is a bug in %1. Seek advice from your local"
335 " %2 guru.")
336 .arg(args&: s_programName, args&: s_programName));
337}
338
339/*!
340 Formats \a message and \a details into a single string
341 and outputs that string to \c stderr. \a type specifies
342 whether the \a message is an error or a warning.
343 */
344void Location::emitMessage(MessageType type, const QString &message, const QString &details) const
345{
346 if (type == Warning && s_spuriousRegExp != nullptr) {
347 auto match = s_spuriousRegExp->match(subject: message, offset: 0, matchType: QRegularExpression::NormalMatch,
348 matchOptions: QRegularExpression::AnchorAtOffsetMatchOption);
349 if (match.hasMatch() && match.capturedLength() == message.size())
350 return;
351 }
352
353 QString result = message;
354 if (!details.isEmpty())
355 result += "\n[" + details + QLatin1Char(']');
356 result.replace(before: "\n", after: "\n ");
357 if (isEmpty()) {
358 if (type == Error)
359 result.prepend(QStringLiteral(": error: "));
360 else if (type == Warning) {
361 result.prepend(QStringLiteral(": warning: "));
362 ++s_warningCount;
363 }
364 } else {
365 if (type == Error)
366 result.prepend(QStringLiteral(": (qdoc) error: "));
367 else if (type == Warning) {
368 result.prepend(QStringLiteral(": (qdoc) warning: "));
369 ++s_warningCount;
370 }
371 }
372 if (type != Report)
373 result.prepend(s: toString());
374 fprintf(stderr, format: "%s\n", result.toLatin1().data());
375 fflush(stderr);
376}
377
378/*!
379 Converts the location to a string to be prepended to error
380 messages.
381 */
382QString Location::toString() const
383{
384 QString str;
385
386 if (isEmpty()) {
387 str = s_programName;
388 } else {
389 Location loc2 = *this;
390 loc2.setEtc(false);
391 loc2.pop();
392 if (!loc2.isEmpty()) {
393 QString blah = QStringLiteral("In file included from ");
394 for (;;) {
395 str += blah;
396 str += loc2.top();
397 loc2.pop();
398 if (loc2.isEmpty())
399 break;
400 str += QStringLiteral(",\n");
401 blah.fill(c: ' ');
402 }
403 str += QStringLiteral(":\n");
404 }
405 str += top();
406 }
407 return str;
408}
409
410QString Location::top() const
411{
412 QDir path(filePath());
413 QString str = path.absolutePath();
414 if (lineNo() >= 1) {
415 str += QLatin1Char(':');
416 str += QString::number(lineNo());
417 }
418 if (etc())
419 str += QLatin1String(" (etc.)");
420 return str;
421}
422
423QT_END_NAMESPACE
424

source code of qttools/src/qdoc/qdoc/location.cpp