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 a reference to the list of topic commands used in the
235 current qdoc comment. Normally there is only one, but there
236 can be multiple \e{qmlproperty} commands, for example.
237 */
238TopicList Doc::topicsUsed() const
239{
240 return m_priv == nullptr ? TopicList() : m_priv->m_topics;
241}
242
243ArgList Doc::metaCommandArgs(const QString &metacommand) const
244{
245 return m_priv == nullptr ? ArgList() : m_priv->m_metaCommandMap.value(key: metacommand);
246}
247
248QList<Text> Doc::alsoList() const
249{
250 return m_priv == nullptr ? QList<Text>() : m_priv->m_alsoList;
251}
252
253bool Doc::hasTableOfContents() const
254{
255 return m_priv && m_priv->extra && !m_priv->extra->m_tableOfContents.isEmpty();
256}
257
258bool Doc::hasKeywords() const
259{
260 return m_priv && m_priv->extra && !m_priv->extra->m_keywords.isEmpty();
261}
262
263bool Doc::hasTargets() const
264{
265 return m_priv && m_priv->extra && !m_priv->extra->m_targets.isEmpty();
266}
267
268const QList<Atom *> &Doc::tableOfContents() const
269{
270 m_priv->constructExtra();
271 return m_priv->extra->m_tableOfContents;
272}
273
274const QList<int> &Doc::tableOfContentsLevels() const
275{
276 m_priv->constructExtra();
277 return m_priv->extra->m_tableOfContentsLevels;
278}
279
280const QList<Atom *> &Doc::keywords() const
281{
282 m_priv->constructExtra();
283 return m_priv->extra->m_keywords;
284}
285
286const QList<Atom *> &Doc::targets() const
287{
288 m_priv->constructExtra();
289 return m_priv->extra->m_targets;
290}
291
292QStringMultiMap *Doc::metaTagMap() const
293{
294 return m_priv && m_priv->extra ? &m_priv->extra->m_metaMap : nullptr;
295}
296
297void Doc::initialize(FileResolver& file_resolver)
298{
299 Config &config = Config::instance();
300 DocParser::initialize(config, file_resolver);
301
302 const auto &configMacros = config.subVars(CONFIG_MACRO);
303 for (const auto &macroName : configMacros) {
304 QString macroDotName = CONFIG_MACRO + Config::dot + macroName;
305 Macro macro;
306 macro.numParams = -1;
307 const auto &macroConfigVar = config.get(var: macroDotName);
308 macro.m_defaultDef = macroConfigVar.asString();
309 if (!macro.m_defaultDef.isEmpty()) {
310 macro.m_defaultDefLocation = macroConfigVar.location();
311 macro.numParams = Config::numParams(value: macro.m_defaultDef);
312 }
313 bool silent = false;
314
315 const auto &macroDotNames = config.subVars(var: macroDotName);
316 for (const auto &f : macroDotNames) {
317 const auto &macroSubVar = config.get(var: macroDotName + Config::dot + f);
318 QString def{macroSubVar.asString()};
319 if (!def.isEmpty()) {
320 macro.m_otherDefs.insert(key: f, value: def);
321 int m = Config::numParams(value: def);
322 if (macro.numParams == -1)
323 macro.numParams = m;
324 // .match definition is a regular expression that contains no params
325 else if (macro.numParams != m && f != QLatin1String("match")) {
326 if (!silent) {
327 QString other = QStringLiteral("default");
328 if (macro.m_defaultDef.isEmpty())
329 other = macro.m_otherDefs.constBegin().key();
330 macroSubVar.location().warning(
331 QStringLiteral("Macro '\\%1' takes inconsistent number of "
332 "arguments (%2 %3, %4 %5)")
333 .arg(args: macroName, args: f, args: QString::number(m), args&: other,
334 args: QString::number(macro.numParams)));
335 silent = true;
336 }
337 if (macro.numParams < m)
338 macro.numParams = m;
339 }
340 }
341 }
342 if (macro.numParams != -1)
343 m_utilities.macroHash.insert(key: macroName, value: macro);
344 }
345}
346
347/*!
348 All the heap allocated variables are deleted.
349 */
350void Doc::terminate()
351{
352 m_utilities.cmdHash.clear();
353 m_utilities.macroHash.clear();
354}
355
356/*!
357 Trims the deadwood out of \a str. i.e., this function
358 cleans up \a str.
359 */
360void Doc::trimCStyleComment(Location &location, QString &str)
361{
362 QString cleaned;
363 Location m = location;
364 bool metAsterColumn = true;
365 int asterColumn = location.columnNo() + 1;
366 int i;
367
368 for (i = 0; i < str.size(); ++i) {
369 if (m.columnNo() == asterColumn) {
370 if (str[i] != '*')
371 break;
372 cleaned += ' ';
373 metAsterColumn = true;
374 } else {
375 if (str[i] == '\n') {
376 if (!metAsterColumn)
377 break;
378 metAsterColumn = false;
379 }
380 cleaned += str[i];
381 }
382 m.advance(ch: str[i]);
383 }
384 if (cleaned.size() == str.size())
385 str = cleaned;
386
387 for (int i = 0; i < 3; ++i)
388 location.advance(ch: str[i]);
389 str = str.mid(position: 3, n: str.size() - 5);
390}
391
392CodeMarker *Doc::quoteFromFile(const Location &location, Quoter &quoter, ResolvedFile resolved_file)
393{
394 // TODO: quoteFromFile should not care about modifying a stateful
395 // quoter from the outside, instead, it should produce a quoter
396 // that allows the caller to retrieve the required information
397 // about the quoted file.
398 //
399 // When changing the way in which quoting works, this kind of
400 // spread resposability should be removed, together with quoteFromFile.
401 quoter.reset();
402
403 QString code;
404 {
405 QFile input_file{resolved_file.get_path()};
406 input_file.open(flags: QFile::ReadOnly);
407 code = DocParser::untabifyEtc(str: QTextStream{&input_file}.readAll());
408 }
409
410 CodeMarker *marker = CodeMarker::markerForFileName(fileName: resolved_file.get_path());
411 quoter.quoteFromFile(userFriendlyFileName: resolved_file.get_path(), plainCode: code, markedCode: marker->markedUpCode(code, nullptr, location));
412 return marker;
413}
414
415void Doc::detach()
416{
417 if (m_priv == nullptr) {
418 m_priv = new DocPrivate;
419 return;
420 }
421 if (m_priv->count == 1)
422 return;
423
424 --m_priv->count;
425
426 auto *newPriv = new DocPrivate(*m_priv);
427 newPriv->count = 1;
428 if (m_priv->extra)
429 newPriv->extra = new DocPrivateExtra(*m_priv->extra);
430
431 m_priv = newPriv;
432}
433
434QT_END_NAMESPACE
435

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