Skip to content

Commit 91f4643

Browse files
astashovkeertip
authored andcommitted
Add macros support [#1264] (#1320)
This adds pretty primitive macros support. Usage: You define the template first: /// {@template foo} /// Some content /// {@endtemplate} Then you use it like: /// Some stuff /// {@macro foo} /// More stuff and it will be displayed as: /// Some stuff /// Some content /// More stuff You can define the template pretty much in any documentable symbol comments. So, after we build a `Package` object, we go through all the comments documentation of all the model elements of the package, and build the index of macros. It's still quite fast, and doesn't increase the build times e.g. of the Flutter docs dramatically. So, after that, when we actually build HTML pages, we already have the index with templates in `Package`, so we can replace `{@macros` entries with the corresponding contents from `{@template`
1 parent 7478a4f commit 91f4643

36 files changed

+537
-5
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ authoring doc comments for Dart with `dartdoc`.
9393
`dartdoc` will not generate documentation for a Dart element and its children that have the
9494
`@nodoc` tag in the documentation comment.
9595

96+
## Advanced features
97+
98+
### Macros
99+
100+
You can specify "macros", i.e. reusable pieces of documentation. For that, first specify a template
101+
anywhere in the comments, like:
102+
103+
```
104+
/// {@template template_name}
105+
/// Some shared docs
106+
/// {@endtemplate}
107+
```
108+
109+
and then you can insert it via `{@macro template_name}`, like
110+
111+
```
112+
/// Some comment
113+
/// {@macro template_name}
114+
/// More comments
115+
```
116+
96117
## Issues and bugs
97118

98119
Please file reports on the [GitHub Issue Tracker][].

lib/dartdoc.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ class DartDoc {
150150
}
151151

152152
Package package = new Package(libraries, packageMeta);
153+
// Go through docs of every model element in package to prebuild the macros index
154+
package.allModelElements.forEach((m) => m.documentation);
153155

154156
// Create the out directory.
155157
if (!outputDir.existsSync()) outputDir.createSync(recursive: true);

lib/src/model.dart

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1472,6 +1472,8 @@ abstract class ModelElement implements Comparable, Nameable, Documentable {
14721472

14731473
_rawDocs = stripComments(_rawDocs) ?? '';
14741474
_rawDocs = _injectExamples(_rawDocs);
1475+
_rawDocs = _stripMacroTemplatesAndAddToIndex(_rawDocs);
1476+
_rawDocs = _injectMacros(_rawDocs);
14751477
return _rawDocs;
14761478
}
14771479

@@ -1867,6 +1869,55 @@ abstract class ModelElement implements Comparable, Nameable, Documentable {
18671869
});
18681870
}
18691871

1872+
/// Replace {@macro ...} in API comments with the contents of the macro
1873+
///
1874+
/// Syntax:
1875+
///
1876+
/// {@macro NAME}
1877+
///
1878+
/// Example:
1879+
///
1880+
/// You define the template anywhere in the comments like:
1881+
///
1882+
/// {@template foo}
1883+
/// Foo contents!
1884+
/// {@endtemplate}
1885+
///
1886+
/// and them somewhere use it like this:
1887+
///
1888+
/// Some comments
1889+
/// {@macro foo}
1890+
/// More comments
1891+
///
1892+
/// Which will render
1893+
///
1894+
/// Some comments
1895+
/// Foo contents!
1896+
/// More comments
1897+
///
1898+
String _injectMacros(String rawDocs) {
1899+
final macroRegExp = new RegExp(r'{@macro\s+([^}]+)}');
1900+
return rawDocs.replaceAllMapped(macroRegExp, (match) {
1901+
return package.getMacro(match[1]);
1902+
});
1903+
}
1904+
1905+
/// Parse {@template ...} in API comments and store them in the index on the package.
1906+
///
1907+
/// Syntax:
1908+
///
1909+
/// {@template NAME}
1910+
/// The contents of the macro
1911+
/// {@endtemplate}
1912+
///
1913+
String _stripMacroTemplatesAndAddToIndex(String rawDocs) {
1914+
final templateRegExp = new RegExp(r'[ ]*{@template\s+(.+?)}([\s\S]+?){@endtemplate}[ ]*\n?', multiLine: true);
1915+
return rawDocs.replaceAllMapped(templateRegExp, (match) {
1916+
package.addMacro(match[1].trim(), match[2].trim());
1917+
return "";
1918+
});
1919+
}
1920+
18701921
/// Helper for _injectExamples used to process @example arguments.
18711922
/// Returns a map of arguments. The first unnamed argument will have key 'src'.
18721923
/// The computed file path, constructed from 'src' and 'region' will have key
@@ -1899,7 +1950,7 @@ abstract class ModelElement implements Comparable, Nameable, Documentable {
18991950
var ext = p.extension(src);
19001951
file = p.join(dir, '$basename-$region$ext$fragExtension');
19011952
}
1902-
args['file'] = config.examplePathPrefix == null ? file : p.join(config.examplePathPrefix, file);
1953+
args['file'] = config?.examplePathPrefix == null ? file : p.join(config.examplePathPrefix, file);
19031954
return args;
19041955
}
19051956
}
@@ -2020,6 +2071,7 @@ class Package implements Nameable, Documentable {
20202071
final PackageMeta packageMeta;
20212072
final Map<String, Library> elementLibaryMap = {};
20222073
String _docsAsHtml;
2074+
final Map<String, String> _macros = {};
20232075

20242076
Package(Iterable<LibraryElement> libraryElements, this.packageMeta) {
20252077
libraryElements.forEach((element) {
@@ -2172,6 +2224,12 @@ class Package implements Nameable, Documentable {
21722224
ExportGraph get exportGraph {
21732225
return (_exportGraph ??= new ExportGraph(libraries.map((l) => l.element as LibraryElement)));
21742226
}
2227+
2228+
String getMacro(String name) => _macros[name];
2229+
2230+
void addMacro(String name, String content) {
2231+
_macros[name] = content;
2232+
}
21752233
}
21762234

21772235
class PackageCategory implements Comparable<PackageCategory> {

test/model_test.dart

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,26 @@ void main() {
203203
});
204204
});
205205

206+
group('Macros', () {
207+
Class dog;
208+
Method withMacro, withMacro2;
209+
210+
setUp(() {
211+
dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
212+
withMacro = dog.allInstanceMethods.firstWhere((m) => m.name == 'withMacro');
213+
withMacro2 = dog.allInstanceMethods.firstWhere((m) => m.name == 'withMacro2');
214+
package.allModelElements.forEach((m) => m.documentation);
215+
});
216+
217+
test("renders a macro within the same comment where it's defined", () {
218+
expect(withMacro.documentation, equals("Macro method\n\n\nFoo macro content\nMore docs"));
219+
});
220+
221+
test("renders a macro in another method, not the same where it's defined", () {
222+
expect(withMacro2.documentation, equals("Foo macro content"));
223+
});
224+
});
225+
206226
group('Docs as HTML', () {
207227
Class Apple, B, superAwesomeClass, foo2;
208228
TopLevelVariable incorrectDocReferenceFromEx;
@@ -596,7 +616,7 @@ void main() {
596616
});
597617

598618
test('get methods', () {
599-
expect(Dog.instanceMethods, hasLength(6));
619+
expect(Dog.instanceMethods, hasLength(8));
600620
});
601621

602622
test('get operators', () {
@@ -645,7 +665,7 @@ void main() {
645665
});
646666

647667
test('F has many inherited methods', () {
648-
expect(F.inheritedMethods, hasLength(9));
668+
expect(F.inheritedMethods, hasLength(11));
649669
expect(
650670
F.inheritedMethods.map((im) => im.name),
651671
equals([
@@ -657,7 +677,9 @@ void main() {
657677
'testGeneric',
658678
'testGenericMethod',
659679
'testMethod',
660-
'toString'
680+
'toString',
681+
'withMacro',
682+
'withMacro2'
661683
]));
662684
});
663685

testing/test_package/lib/example.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,19 @@ class Dog implements Cat, E {
249249
return [new Apple()];
250250
}
251251

252+
/// Macro method
253+
///
254+
/// {@template foo}
255+
/// Foo macro content
256+
/// {@endtemplate}
257+
///
258+
/// {@macro foo}
259+
/// More docs
260+
void withMacro() {}
261+
262+
/// {@macro foo}
263+
void withMacro2() {}
264+
252265
void testGeneric(Map<String, dynamic> args) {}
253266

254267
void testMethod(Iterable it) {}

testing/test_package_docs/ex/Dog-class.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,22 @@ <h2>Methods</h2>
366366
<p>Returns a string representation of this object.</p>
367367
<div class="features">inherited</div>
368368
</dd>
369+
<dt id="withMacro" class="callable">
370+
<span class="name"><a href="ex/Dog/withMacro.html">withMacro</a></span><span class="signature">(<wbr>)
371+
<span class="returntype parameter">&#8594; void</span>
372+
</span>
373+
</dt>
374+
<dd>
375+
<p>Macro method</p>
376+
</dd>
377+
<dt id="withMacro2" class="callable">
378+
<span class="name"><a href="ex/Dog/withMacro2.html">withMacro2</a></span><span class="signature">(<wbr>)
379+
<span class="returntype parameter">&#8594; void</span>
380+
</span>
381+
</dt>
382+
<dd>
383+
<p>Foo macro content</p>
384+
</dd>
369385
</dl>
370386
</section>
371387

@@ -406,6 +422,8 @@ <h5>Dog</h5>
406422
<li><a href="ex/Dog/testGenericMethod.html">testGenericMethod</a></li>
407423
<li><a href="ex/Dog/testMethod.html">testMethod</a></li>
408424
<li class="inherited"><a href="ex/Dog/toString.html">toString</a></li>
425+
<li><a href="ex/Dog/withMacro.html">withMacro</a></li>
426+
<li><a href="ex/Dog/withMacro2.html">withMacro2</a></li>
409427
</ol>
410428
</div><!--/.sidebar-offcanvas-->
411429

testing/test_package_docs/ex/Dog/Dog.deprecatedCreate.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ <h5><a href="ex/Dog-class.html">Dog</a></h5>
106106
<li><a href="ex/Dog/testGenericMethod.html">testGenericMethod</a></li>
107107
<li><a href="ex/Dog/testMethod.html">testMethod</a></li>
108108
<li class="inherited"><a href="ex/Dog/toString.html">toString</a></li>
109+
<li><a href="ex/Dog/withMacro.html">withMacro</a></li>
110+
<li><a href="ex/Dog/withMacro2.html">withMacro2</a></li>
109111
</ol>
110112

111113
</div><!--/.sidebar-offcanvas-left-->

testing/test_package_docs/ex/Dog/Dog.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ <h5><a href="ex/Dog-class.html">Dog</a></h5>
106106
<li><a href="ex/Dog/testGenericMethod.html">testGenericMethod</a></li>
107107
<li><a href="ex/Dog/testMethod.html">testMethod</a></li>
108108
<li class="inherited"><a href="ex/Dog/toString.html">toString</a></li>
109+
<li><a href="ex/Dog/withMacro.html">withMacro</a></li>
110+
<li><a href="ex/Dog/withMacro2.html">withMacro2</a></li>
109111
</ol>
110112

111113
</div><!--/.sidebar-offcanvas-left-->

testing/test_package_docs/ex/Dog/abstractMethod.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ <h5><a href="ex/Dog-class.html">Dog</a></h5>
105105
<li><a href="ex/Dog/testGenericMethod.html">testGenericMethod</a></li>
106106
<li><a href="ex/Dog/testMethod.html">testMethod</a></li>
107107
<li class="inherited"><a href="ex/Dog/toString.html">toString</a></li>
108+
<li><a href="ex/Dog/withMacro.html">withMacro</a></li>
109+
<li><a href="ex/Dog/withMacro2.html">withMacro2</a></li>
108110
</ol>
109111

110112
</div><!--/.sidebar-offcanvas-->

testing/test_package_docs/ex/Dog/createDog.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ <h5><a href="ex/Dog-class.html">Dog</a></h5>
105105
<li><a href="ex/Dog/testGenericMethod.html">testGenericMethod</a></li>
106106
<li><a href="ex/Dog/testMethod.html">testMethod</a></li>
107107
<li class="inherited"><a href="ex/Dog/toString.html">toString</a></li>
108+
<li><a href="ex/Dog/withMacro.html">withMacro</a></li>
109+
<li><a href="ex/Dog/withMacro2.html">withMacro2</a></li>
108110
</ol>
109111

110112
</div><!--/.sidebar-offcanvas-->

0 commit comments

Comments
 (0)