// 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:path/path.dart' as path; /// Scans the dartdoc HTML output in the provided `htmlOutputPath` 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 sections. /// /// The string `{@` is allowed in sections, because those may contain /// sample code where the sequence is perfectly legal, e.g. for required named /// parameters of a method: /// /// ``` /// void foo({@required int bar}); /// ``` void checkForUnresolvedDirectives(String htmlOutputPath) { final Directory dartDocDir = Directory(htmlOutputPath); if (!dartDocDir.existsSync()) { throw Exception('Directory with dartdoc output (${dartDocDir.path}) does not exist.'); } // Makes sure that the path we were given contains some of the expected // libraries and HTML files. final List canaryLibraries = [ 'animation', 'cupertino', 'material', 'widgets', 'rendering', 'flutter_driver', ]; final List canaryFiles = [ 'Widget-class.html', 'Material-class.html', 'Canvas-class.html', ]; print('Scanning for unresolved dartdoc directives...'); final List toScan = dartDocDir.listSync(); int 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 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 String match in matches) { stderr.writeln(' $match'); } } return matches.length; } // Matches all `{@` that are not within `` sections. // // This regex may lead to false positives if the docs ever contain nested tags // inside 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 // sections. final RegExp _pattern = RegExp(r'({@[^}\n]*}?)(?![^<>]* 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(args.single); }