Skip to content

Commit e17a721

Browse files
authored
Ban unresolved dartdoc directives from HTML output (flutter#62167)
1 parent 2273961 commit e17a721

File tree

7 files changed

+134
-8
lines changed

7 files changed

+134
-8
lines changed

dev/tools/dartdoc.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import 'package:intl/intl.dart';
1111
import 'package:path/path.dart' as path;
1212
import 'package:process/process.dart';
1313

14+
import 'dartdoc_checker.dart';
15+
1416
const String kDocsRoot = 'dev/docs';
1517
const String kPublishRoot = '$kDocsRoot/doc';
1618
const String kSnippetsRoot = 'dev/snippets';
@@ -222,6 +224,7 @@ Future<void> main(List<String> arguments) async {
222224
exit(exitCode);
223225

224226
sanityCheckDocs();
227+
checkForUnresolvedDirectives('$kPublishRoot/api');
225228

226229
createIndexAndCleanup();
227230
}

dev/tools/dartdoc_checker.dart

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:path/path.dart' as path;
8+
9+
/// Scans the dartdoc HTML output in the provided `htmlOutputPath` for
10+
/// unresolved dartdoc directives (`{@foo x y}`).
11+
///
12+
/// Dartdoc usually replaces those directives with other content. However,
13+
/// if the directive is misspelled (or contains other errors) it is placed
14+
/// verbatim into the HTML output. That's not desirable and this check verifies
15+
/// that no directives appear verbatim in the output by checking that the
16+
/// string `{@` does not appear in the HTML output outside of <code> sections.
17+
///
18+
/// The string `{@` is allowed in <code> sections, because those may contain
19+
/// sample code where the sequence is perfectly legal, e.g. for required named
20+
/// parameters of a method:
21+
///
22+
/// ```
23+
/// void foo({@required int bar});
24+
/// ```
25+
void checkForUnresolvedDirectives(String htmlOutputPath) {
26+
final Directory dartDocDir = Directory(htmlOutputPath);
27+
if (!dartDocDir.existsSync()) {
28+
throw Exception('Directory with dartdoc output (${dartDocDir.path}) does not exist.');
29+
}
30+
31+
// Makes sure that the path we were given contains some of the expected
32+
// libraries and HTML files.
33+
final List<String> canaryLibraries = <String>[
34+
'animation',
35+
'cupertino',
36+
'material',
37+
'widgets',
38+
'rendering',
39+
'flutter_driver',
40+
];
41+
final List<String> canaryFiles = <String>[
42+
'Widget-class.html',
43+
'Material-class.html',
44+
'Canvas-class.html',
45+
];
46+
47+
print('Scanning for unresolved dartdoc directives...');
48+
49+
final List<FileSystemEntity> toScan = dartDocDir.listSync();
50+
int count = 0;
51+
52+
while (toScan.isNotEmpty) {
53+
final FileSystemEntity entity = toScan.removeLast();
54+
if (entity is File) {
55+
if (path.extension(entity.path) != '.html') {
56+
continue;
57+
}
58+
canaryFiles.remove(path.basename(entity.path));
59+
60+
// TODO(goderbauer): Remove this exception when https://github.com/dart-lang/dartdoc/issues/2272 is fixed.
61+
if (entity.path.endsWith('-class.html') || entity.path.endsWith('-library.html') ) {
62+
continue;
63+
}
64+
65+
count += _scanFile(entity);
66+
} else if (entity is Directory) {
67+
canaryLibraries.remove(path.basename(entity.path));
68+
toScan.addAll(entity.listSync());
69+
} else {
70+
throw Exception('$entity is neither file nor directory.');
71+
}
72+
}
73+
74+
if (canaryLibraries.isNotEmpty) {
75+
throw Exception('Did not find docs for the following libraries: ${canaryLibraries.join(', ')}.');
76+
}
77+
if (canaryFiles.isNotEmpty) {
78+
throw Exception('Did not find docs for the following files: ${canaryFiles.join(', ')}.');
79+
}
80+
if (count > 0) {
81+
throw Exception('Found $count unresolved dartdoc directives (see log above).');
82+
}
83+
print('No unresolved dartdoc directives detected.');
84+
}
85+
86+
int _scanFile(File file) {
87+
assert(path.extension(file.path) == 'html');
88+
Iterable<String> matches = _pattern.allMatches(file.readAsStringSync())
89+
.map((RegExpMatch m ) => m.group(0));
90+
91+
// TODO(goderbauer): Remove this exception when https://github.com/dart-lang/dartdoc/issues/1945 is fixed.
92+
matches = matches
93+
.where((String m) => m != '{@inject-html}')
94+
.where((String m) => m != '{@end-inject-html}');
95+
96+
if (matches.isNotEmpty) {
97+
stderr.writeln('Found unresolved dartdoc directives in ${file.path}:');
98+
for (final String match in matches) {
99+
stderr.writeln(' $match');
100+
}
101+
}
102+
return matches.length;
103+
}
104+
105+
// Matches all `{@` that are not within `<code></code>` sections.
106+
//
107+
// This regex may lead to false positives if the docs ever contain nested tags
108+
// inside <code> sections. Since we currently don't do that, doing the matching
109+
// with a regex is a lot faster than using an HTML parser to strip out the
110+
// <code> sections.
111+
final RegExp _pattern = RegExp(r'({@[^}\n]*}?)(?![^<>]*</code)');
112+
113+
// Usually, the checker is invoked directly from `dartdoc.dart`. Main method
114+
// is included for convenient local runs without having to regenerate
115+
// the dartdocs every time.
116+
//
117+
// Provide the path to the dartdoc HTML output as an argument when running the
118+
// program.
119+
void main(List<String> args) {
120+
if (args.length != 1) {
121+
throw Exception('Must provide the path to the dartdoc HTML output as argument.');
122+
}
123+
if (!Directory(args.single).existsSync()) {
124+
throw Exception('The dartdoc HTML output directory ${args.single} does not exist.');
125+
}
126+
checkForUnresolvedDirectives(args.single);
127+
}

packages/flutter/lib/src/material/ink_well.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ class InkResponse extends StatelessWidget {
562562
/// {@macro flutter.widgets.Focus.focusNode}
563563
final FocusNode focusNode;
564564

565-
/// {@template flutter.widgets.Focus.canRequestFocus}
565+
/// {@macro flutter.widgets.Focus.canRequestFocus}
566566
final bool canRequestFocus;
567567

568568
/// The rectangle to use for the highlight effect and for clipping

packages/flutter/lib/src/painting/gradient.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ abstract class GradientTransform {
9494
/// transform: GradientRotation(math.pi/4),
9595
/// );
9696
/// ```
97+
/// {@end-tool}
9798
@immutable
9899
class GradientRotation extends GradientTransform {
99100
/// Constructs a [GradientRotation] for the specified angle.

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -652,11 +652,6 @@ class EditableText extends StatefulWidget {
652652
/// text.
653653
///
654654
/// Defaults to the ambient [Directionality], if any.
655-
///
656-
/// See also:
657-
///
658-
/// * {@macro flutter.gestures.monodrag.dragStartExample}
659-
///
660655
/// {@endtemplate}
661656
final TextDirection textDirection;
662657

packages/flutter/lib/src/widgets/scroll_physics.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ class RangeMaintainingScrollPhysics extends ScrollPhysics {
457457
/// ```dart
458458
/// BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics())
459459
/// ```
460-
/// (@end-tool}
460+
/// {@end-tool}
461461
///
462462
/// See also:
463463
///

packages/flutter/lib/src/widgets/ticker_provider.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class TickerMode extends StatelessWidget {
4343

4444
/// The widget below this widget in the tree.
4545
///
46-
/// {@template flutter.widgets.child}
46+
/// {@macro flutter.widgets.child}
4747
final Widget child;
4848

4949
/// Whether tickers in the given subtree should be enabled or disabled.

0 commit comments

Comments
 (0)