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

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