| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:io'; | |
| import 'package:meta/meta.dart'; | |
| import 'package:path/path.dart' as path; | |
| /// Makes sure that the path we were given contains some of the expected | |
| /// libraries. | |
| @visibleForTesting | |
| const List<String> dartdocDirectiveCanaryLibraries = <String>[ | |
| 'animation', | |
| 'cupertino', | |
| 'material', | |
| 'widgets', | |
| 'rendering', | |
| 'flutter_driver', | |
| ]; | |
| /// Makes sure that the path we were given contains some of the expected | |
| /// HTML files. | |
| @visibleForTesting | |
| const List<String> dartdocDirectiveCanaryFiles = <String>[ | |
| 'Widget-class.html', | |
| 'Material-class.html', | |
| 'Canvas-class.html', | |
| ]; | |
| /// Scans the dartdoc HTML output in the provided `dartDocDir` for | |
| /// unresolved dartdoc directives (`{@foo x y}`). | |
| /// | |
| /// Dartdoc usually replaces those directives with other content. However, | |
| /// if the directive is misspelled (or contains other errors) it is placed | |
| /// verbatim into the HTML output. That's not desirable and this check verifies | |
| /// that no directives appear verbatim in the output by checking that the | |
| /// string `{@` does not appear in the HTML output outside of <code> sections. | |
| /// | |
| /// The string `{@` is allowed in <code> sections, because those may contain | |
| /// sample code where the sequence is perfectly legal, e.g. for required named | |
| /// parameters of a method: | |
| /// | |
| /// ```dart | |
| /// void foo({@required int bar}); | |
| /// ``` | |
| void checkForUnresolvedDirectives(Directory dartDocDir) { | |
| if (!dartDocDir.existsSync()) { | |
| throw Exception('Directory with dartdoc output (${dartDocDir.path}) does not exist.'); | |
| } | |
| // Make a copy since this will be mutated | |
| final List<String> canaryLibraries = dartdocDirectiveCanaryLibraries.toList(); | |
| final List<String> canaryFiles = dartdocDirectiveCanaryFiles.toList(); | |
| print('Scanning for unresolved dartdoc directives...'); | |
| final List<FileSystemEntity> toScan = dartDocDir.listSync(); | |
| var count = 0; | |
| while (toScan.isNotEmpty) { | |
| final FileSystemEntity entity = toScan.removeLast(); | |
| if (entity is File) { | |
| if (path.extension(entity.path) != '.html') { | |
| continue; | |
| } | |
| canaryFiles.remove(path.basename(entity.path)); | |
| count += _scanFile(entity); | |
| } else if (entity is Directory) { | |
| canaryLibraries.remove(path.basename(entity.path)); | |
| toScan.addAll(entity.listSync()); | |
| } else { | |
| throw Exception('$entity is neither file nor directory.'); | |
| } | |
| } | |
| if (canaryLibraries.isNotEmpty) { | |
| throw Exception( | |
| 'Did not find docs for the following libraries: ${canaryLibraries.join(', ')}.', | |
| ); | |
| } | |
| if (canaryFiles.isNotEmpty) { | |
| throw Exception('Did not find docs for the following files: ${canaryFiles.join(', ')}.'); | |
| } | |
| if (count > 0) { | |
| throw Exception('Found $count unresolved dartdoc directives (see log above).'); | |
| } | |
| print('No unresolved dartdoc directives detected.'); | |
| } | |
| int _scanFile(File file) { | |
| assert(path.extension(file.path) == '.html'); | |
| final Iterable<String> matches = _pattern | |
| .allMatches(file.readAsStringSync()) | |
| .map((RegExpMatch m) => m.group(0)!); | |
| if (matches.isNotEmpty) { | |
| stderr.writeln('Found unresolved dartdoc directives in ${file.path}:'); | |
| for (final match in matches) { | |
| stderr.writeln(' $match'); | |
| } | |
| } | |
| return matches.length; | |
| } | |
| // Matches all `{@` that are not within `<code></code>` sections. | |
| // | |
| // This regex may lead to false positives if the docs ever contain nested tags | |
| // inside <code> sections. Since we currently don't do that, doing the matching | |
| // with a regex is a lot faster than using an HTML parser to strip out the | |
| // <code> sections. | |
| final RegExp _pattern = RegExp(r'({@[^}\n]*}?)(?![^<>]*</code)'); | |
| // Usually, the checker is invoked directly from `dartdoc.dart`. Main method | |
| // is included for convenient local runs without having to regenerate | |
| // the dartdocs every time. | |
| // | |
| // Provide the path to the dartdoc HTML output as an argument when running the | |
| // program. | |
| void main(List<String> args) { | |
| if (args.length != 1) { | |
| throw Exception('Must provide the path to the dartdoc HTML output as argument.'); | |
| } | |
| if (!Directory(args.single).existsSync()) { | |
| throw Exception('The dartdoc HTML output directory ${args.single} does not exist.'); | |
| } | |
| checkForUnresolvedDirectives(Directory(args.single)); | |
| } |