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 "doc.h"
5
6#include "atom.h"
7#include "config.h"
8#include "codemarker.h"
9#include "docparser.h"
10#include "docprivate.h"
11#include "generator.h"
12#include "qmltypenode.h"
13#include "quoter.h"
14#include "text.h"
15#include "utilities.h"
16
17#include <qcryptographichash.h>
18
19QT_BEGIN_NAMESPACE
20
21using namespace Qt::StringLiterals;
22
23DocUtilities &Doc::m_utilities = DocUtilities::instance();
24
25/*!
26 \typedef ArgList
27 \relates Doc
28
29 A list of metacommand arguments that appear in a Doc. Each entry
30 in the list is a <QString, QString> pair (ArgPair):
31
32 \list
33 \li \c {ArgPair.first} - arguments passed to the command.
34 \li \c {ArgPair.second} - optional argument string passed
35 within brackets immediately following the command.
36 \endlist
37*/
38
39/*!
40 Parse the qdoc comment \a source. Build up a list of all the topic
41 commands found including their arguments. This constructor is used
42 when there can be more than one topic command in theqdoc comment.
43 Normally, there is only one topic command in a qdoc comment, but in
44 QML documentation, there is the case where the qdoc \e{qmlproperty}
45 command can appear multiple times in a qdoc comment.
46 */
47Doc::Doc(const Location &start_loc, const Location &end_loc, const QString &source,
48 const QSet<QString> &metaCommandSet, const QSet<QString> &topics)
49{
50 m_priv = new DocPrivate(start_loc, end_loc, source);
51 DocParser parser;
52 parser.parse(source, docPrivate: m_priv, metaCommandSet, possibleTopics: topics);
53
54 if (Config::instance().getAtomsDump()) {
55 start_loc.information(message: u"==== Atoms Structure for block comment starting at %1 ===="_s.arg(
56 a: start_loc.toString()));
57 body().dump();
58 end_loc.information(
59 message: u"==== Ending atoms Structure for block comment ending at %1 ===="_s.arg(
60 a: end_loc.toString()));
61 }
62}
63
64Doc::Doc(const Doc &doc) : m_priv(nullptr)
65{
66 operator=(doc);
67}
68
69Doc::~Doc()
70{
71 if (m_priv && m_priv->deref())
72 delete m_priv;
73}
74
75Doc &Doc::operator=(const Doc &doc)
76{
77 if (&doc == this)
78 return *this;
79 if (doc.m_priv)
80 doc.m_priv->ref();
81 if (m_priv && m_priv->deref())
82 delete m_priv;
83 m_priv = doc.m_priv;
84 return *this;
85}
86
87/*!
88 Returns the starting location of a qdoc comment.
89 */
90const Location &Doc::location() const
91{
92 static const Location dummy;
93 return m_priv == nullptr ? dummy : m_priv->m_start_loc;
94}
95
96/*!
97 Returns the starting location of a qdoc comment.
98 */
99const Location &Doc::startLocation() const
100{
101 return location();
102}
103
104const QString &Doc::source() const
105{
106 static QString null;
107 return m_priv == nullptr ? null : m_priv->m_src;
108}
109
110bool Doc::isEmpty() const
111{
112 return m_priv == nullptr || m_priv->m_src.isEmpty();
113}
114
115const Text &Doc::body() const
116{
117 static const Text dummy;
118 return m_priv == nullptr ? dummy : m_priv->m_text;
119}
120
121Text Doc::briefText(bool inclusive) const
122{
123 return body().subText(left: Atom::BriefLeft, right: Atom::BriefRight, from: nullptr, inclusive);
124}
125
126Text Doc::trimmedBriefText(const QString &className) const
127{
128 QString classNameOnly = className;
129 if (className.contains(s: "::"))
130 classNameOnly = className.split(sep: "::").last();
131
132 Text originalText = briefText();
133 Text resultText;
134 const Atom *atom = originalText.firstAtom();
135 if (atom) {
136 QString briefStr;
137 QString whats;
138 /*
139 This code is really ugly. The entire \brief business
140 should be rethought.
141 */
142 while (atom) {
143 if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
144 briefStr += atom->string();
145 } else if (atom->type() == Atom::C) {
146 briefStr += Generator::plainCode(markedCode: atom->string());
147 }
148 atom = atom->next();
149 }
150
151 QStringList w = briefStr.split(sep: QLatin1Char(' '));
152 if (!w.isEmpty() && w.first() == "Returns") {
153 } else {
154 if (!w.isEmpty() && w.first() == "The")
155 w.removeFirst();
156
157 if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly))
158 w.removeFirst();
159
160 if (!w.isEmpty()
161 && ((w.first() == "class") || (w.first() == "function") || (w.first() == "macro")
162 || (w.first() == "widget") || (w.first() == "namespace")
163 || (w.first() == "header")))
164 w.removeFirst();
165
166 if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides"))
167 w.removeFirst();
168
169 if (!w.isEmpty() && (w.first() == "a" || w.first() == "an"))
170 w.removeFirst();
171 }
172
173 whats = w.join(sep: ' ');
174
175 if (whats.endsWith(c: QLatin1Char('.')))
176 whats.truncate(pos: whats.size() - 1);
177
178 if (!whats.isEmpty())
179 whats[0] = whats[0].toUpper();
180
181 // ### move this once \brief is abolished for properties
182 resultText << whats;
183 }
184 return resultText;
185}
186
187Text Doc::legaleseText() const
188{
189 if (m_priv == nullptr || !m_priv->m_hasLegalese)
190 return Text();
191 else
192 return body().subText(left: Atom::LegaleseLeft, right: Atom::LegaleseRight);
193}
194
195QSet<QString> Doc::parameterNames() const
196{
197 return m_priv == nullptr ? QSet<QString>() : m_priv->m_params;
198}
199
200QStringList Doc::enumItemNames() const
201{
202 return m_priv == nullptr ? QStringList() : m_priv->m_enumItemList;
203}
204
205QStringList Doc::omitEnumItemNames() const
206{
207 return m_priv == nullptr ? QStringList() : m_priv->m_omitEnumItemList;
208}
209
210QSet<QString> Doc::metaCommandsUsed() const
211{
212 return m_priv == nullptr ? QSet<QString>() : m_priv->m_metacommandsUsed;
213}
214
215/*!
216 Returns true if the set of metacommands used in the doc
217 comment contains \e {internal}.
218 */
219bool Doc::isInternal() const
220{
221 return metaCommandsUsed().contains(value: QLatin1String("internal"));
222}
223
224/*!
225 Returns true if the set of metacommands used in the doc
226 comment contains \e {reimp}.
227 */
228bool Doc::isMarkedReimp() const
229{
230 return metaCommandsUsed().contains(value: QLatin1String("reimp"));
231}
232
233/*!
234 Returns the list of arguments passed to the \c{\overload} command.
235 */
236QList<ArgPair> Doc::overloadList() const
237{
238 return metaCommandArgs(metaCommand: u"overload"_s);
239}
240
241/*!
242 Returns a reference to the list of topic commands used in the
243 current qdoc comment. Normally there is only one, but there
244 can be multiple \e{qmlproperty} commands, for example.
245 */
246TopicList Doc::topicsUsed() const
247{
248 return m_priv == nullptr ? TopicList() : m_priv->m_topics;
249}
250
251ArgList Doc::metaCommandArgs(const QString &metacommand) const
252{
253 return m_priv == nullptr ? ArgList() : m_priv->m_metaCommandMap.value(key: metacommand);
254}
255
256QList<Text> Doc::alsoList() const
257{
258 return m_priv == nullptr ? QList<Text>() : m_priv->m_alsoList;
259}
260
261bool Doc::hasTableOfContents() const
262{
263 return m_priv && m_priv->extra && !m_priv->extra->m_tableOfContents.isEmpty();
264}
265
266bool Doc::hasKeywords() const
267{
268 return m_priv && m_priv->extra && !m_priv->extra->m_keywords.isEmpty();
269}
270
271bool Doc::hasTargets() const
272{
273 return m_priv && m_priv->extra && !m_priv->extra->m_targets.isEmpty();
274}
275
276const QList<Atom *> &Doc::tableOfContents() const
277{
278 m_priv->constructExtra();
279 return m_priv->extra->m_tableOfContents;
280}
281
282const QList<int> &Doc::tableOfContentsLevels() const
283{
284 m_priv->constructExtra();
285 return m_priv->extra->m_tableOfContentsLevels;
286}
287
288const QList<Atom *> &Doc::keywords() const
289{
290 m_priv->constructExtra();
291 return m_priv->extra->m_keywords;
292}
293
294const QList<Atom *> &Doc::targets() const
295{
296 m_priv->constructExtra();
297 return m_priv->extra->m_targets;
298}
299
300QStringMultiMap *Doc::metaTagMap() const
301{
302 return m_priv && m_priv->extra ? &m_priv->extra->m_metaMap : nullptr;
303}
304
305QMultiMap<ComparisonCategory, Text> *Doc::comparesWithMap() const
306{
307 return m_priv && m_priv->extra ? &m_priv->extra->m_comparesWithMap : nullptr;
308}
309
310void Doc::constructExtra() const
311{
312 if (m_priv)
313 m_priv->constructExtra();
314}
315
316void Doc::initialize(FileResolver& file_resolver)
317{
318 Config &config = Config::instance();
319 DocParser::initialize(config, file_resolver);
320
321 const auto &configMacros = config.subVars(CONFIG_MACRO);
322 for (const auto &macroName : configMacros) {
323 QString macroDotName = CONFIG_MACRO + Config::dot + macroName;
324 Macro macro;
325 macro.numParams = -1;
326 const auto &macroConfigVar = config.get(var: macroDotName);
327 macro.m_defaultDef = macroConfigVar.asString();
328 if (!macro.m_defaultDef.isEmpty()) {
329 macro.m_defaultDefLocation = macroConfigVar.location();
330 macro.numParams = Config::numParams(value: macro.m_defaultDef);
331 }
332 bool silent = false;
333
334 const auto &macroDotNames = config.subVars(var: macroDotName);
335 for (const auto &f : macroDotNames) {
336 const auto &macroSubVar = config.get(var: macroDotName + Config::dot + f);
337 QString def{macroSubVar.asString()};
338 if (!def.isEmpty()) {
339 macro.m_otherDefs.insert(key: f, value: def);
340 int m = Config::numParams(value: def);
341 if (macro.numParams == -1)
342 macro.numParams = m;
343 // .match definition is a regular expression that contains no params
344 else if (macro.numParams != m && f != QLatin1String("match")) {
345 if (!silent) {
346 QString other = QStringLiteral("default");
347 if (macro.m_defaultDef.isEmpty())
348 other = macro.m_otherDefs.constBegin().key();
349 macroSubVar.location().warning(
350 QStringLiteral("Macro '\\%1' takes inconsistent number of "
351 "arguments (%2 %3, %4 %5)")
352 .arg(args: macroName, args: f, args: QString::number(m), args&: other,
353 args: QString::number(macro.numParams)));
354 silent = true;
355 }
356 if (macro.numParams < m)
357 macro.numParams = m;
358 }
359 }
360 }
361 if (macro.numParams != -1)
362 m_utilities.macroHash.insert(key: macroName, value: macro);
363 }
364}
365
366/*!
367 All the heap allocated variables are deleted.
368 */
369void Doc::terminate()
370{
371 m_utilities.cmdHash.clear();
372 m_utilities.macroHash.clear();
373}
374
375/*!
376 Replaces any asterisks used as a left margin in the comment \a str with
377 spaces then trims the comment syntax from the start and end of the string,
378 leaving only the text content. Updates the \a location to refer to the
379 location of the content in the original file.
380 */
381void Doc::trimCStyleComment(Location &location, QString &str)
382{
383 QString cleaned;
384 Location m = location;
385 bool metMargin = true;
386 int marginColumn = location.columnNo() + 1;
387 int i;
388
389 for (i = 0; i < str.size(); ++i) {
390 if (m.columnNo() == marginColumn) {
391 // Stop cleaning if the expected asterisk was missing.
392 if (str[i] != '*')
393 break;
394 cleaned += ' ';
395 metMargin = true;
396 } else {
397 if (str[i] == '\n') {
398 // Break if the line ends before any asterisks are found.
399 if (!metMargin)
400 break;
401 metMargin = false;
402 }
403 cleaned += str[i];
404 }
405 m.advance(ch: str[i]);
406 }
407 // Only replace the string if a fully cleaned version was created
408 // to avoid producing incomplete or corrupted strings.
409 if (cleaned.size() == str.size())
410 str = std::move(cleaned);
411
412 // Update the location to refer to the start of the comment text.
413 for (int i = 0; i < 3; ++i)
414 location.advance(ch: str[i]);
415
416 // Remove the comment syntax from the start (leading comment marker
417 // and newline) and end (comment marker).
418 str = str.mid(position: 3, n: str.size() - 5);
419}
420
421void Doc::quoteFromFile(const Location &location, Quoter &quoter, ResolvedFile resolved_file)
422{
423 // TODO: quoteFromFile should not care about modifying a stateful
424 // quoter from the outside, instead, it should produce a quoter
425 // that allows the caller to retrieve the required information
426 // about the quoted file.
427 //
428 // When changing the way in which quoting works, this kind of
429 // spread resposability should be removed, together with quoteFromFile.
430 quoter.reset();
431
432 QString code;
433 {
434 QFile input_file{resolved_file.get_path()};
435 if (!input_file.open(flags: QFile::ReadOnly))
436 return;
437 code = DocParser::untabifyEtc(str: QTextStream{&input_file}.readAll());
438 }
439
440 CodeMarker *marker = CodeMarker::markerForFileName(fileName: resolved_file.get_path());
441 quoter.quoteFromFile(userFriendlyFileName: resolved_file.get_path(), plainCode: code, markedCode: marker->markedUpCode(code, nullptr, location));
442}
443
444QT_END_NAMESPACE
445

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