blob: 75fbd2be387618e56342ddafd36d5b281163d6a9 [file] [log] [blame]
Michael Goderbauere17a7212020-07-28 15:26:33 -07001// 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
5import 'dart:io';
6
7import '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/// ```
25void 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));
Michael Goderbauere17a7212020-07-28 15:26:33 -070059 count += _scanFile(entity);
60 } else if (entity is Directory) {
61 canaryLibraries.remove(path.basename(entity.path));
62 toScan.addAll(entity.listSync());
63 } else {
64 throw Exception('$entity is neither file nor directory.');
65 }
66 }
67
68 if (canaryLibraries.isNotEmpty) {
69 throw Exception('Did not find docs for the following libraries: ${canaryLibraries.join(', ')}.');
70 }
71 if (canaryFiles.isNotEmpty) {
72 throw Exception('Did not find docs for the following files: ${canaryFiles.join(', ')}.');
73 }
74 if (count > 0) {
75 throw Exception('Found $count unresolved dartdoc directives (see log above).');
76 }
77 print('No unresolved dartdoc directives detected.');
78}
79
80int _scanFile(File file) {
Michael Goderbauer3e867f72020-08-18 19:11:05 -070081 assert(path.extension(file.path) == '.html');
Michael Goderbauere49a5762020-08-07 20:41:04 -070082 final Iterable<String> matches = _pattern.allMatches(file.readAsStringSync())
Anis Alibegićd90ee212021-06-08 02:09:03 +020083 .map((RegExpMatch m ) => m.group(0)!);
Michael Goderbauere17a7212020-07-28 15:26:33 -070084
Michael Goderbauere17a7212020-07-28 15:26:33 -070085 if (matches.isNotEmpty) {
86 stderr.writeln('Found unresolved dartdoc directives in ${file.path}:');
87 for (final String match in matches) {
88 stderr.writeln(' $match');
89 }
90 }
91 return matches.length;
92}
93
94// Matches all `{@` that are not within `<code></code>` sections.
95//
96// This regex may lead to false positives if the docs ever contain nested tags
97// inside <code> sections. Since we currently don't do that, doing the matching
98// with a regex is a lot faster than using an HTML parser to strip out the
99// <code> sections.
100final RegExp _pattern = RegExp(r'({@[^}\n]*}?)(?![^<>]*</code)');
101
102// Usually, the checker is invoked directly from `dartdoc.dart`. Main method
103// is included for convenient local runs without having to regenerate
104// the dartdocs every time.
105//
106// Provide the path to the dartdoc HTML output as an argument when running the
107// program.
108void main(List<String> args) {
109 if (args.length != 1) {
110 throw Exception('Must provide the path to the dartdoc HTML output as argument.');
111 }
112 if (!Directory(args.single).existsSync()) {
113 throw Exception('The dartdoc HTML output directory ${args.single} does not exist.');
114 }
115 checkForUnresolvedDirectives(args.single);
116}