| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
| // for details. 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:convert'; | |
| import 'package:analyzer/dart/analysis/analysis_context.dart'; | |
| import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; | |
| import 'package:analyzer/dart/analysis/results.dart'; | |
| import 'package:analyzer/error/error.dart'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/file_system/overlay_file_system.dart'; | |
| import 'package:analyzer/file_system/physical_file_system.dart'; | |
| import 'package:analyzer/src/error/codes.dart'; | |
| import 'package:analyzer_utilities/package_root.dart' as package_root; | |
| import 'package:test/test.dart'; | |
| main() async { | |
| SnippetTester tester = SnippetTester(); | |
| await tester.verify(); | |
| if (tester.output.isNotEmpty) { | |
| fail(tester.output.toString()); | |
| } | |
| } | |
| class SnippetTester { | |
| final OverlayResourceProvider provider; | |
| final Folder docFolder; | |
| final String snippetDirPath; | |
| final String snippetPath; | |
| final StringBuffer output = StringBuffer(); | |
| factory SnippetTester() { | |
| var provider = OverlayResourceProvider(PhysicalResourceProvider.INSTANCE); | |
| var packageRoot = provider.pathContext.normalize(package_root.packageRoot); | |
| var analyzerPath = provider.pathContext.join(packageRoot, 'analyzer'); | |
| var docPath = provider.pathContext.join(analyzerPath, 'doc'); | |
| var docFolder = provider.getFolder(docPath); | |
| var snippetDirPath = | |
| provider.pathContext.join(analyzerPath, 'test', 'snippets'); | |
| var snippetPath = provider.pathContext.join(snippetDirPath, 'snippet.dart'); | |
| return SnippetTester._(provider, docFolder, snippetDirPath, snippetPath); | |
| } | |
| SnippetTester._( | |
| this.provider, this.docFolder, this.snippetDirPath, this.snippetPath); | |
| Future<void> verify() async { | |
| await verifyFolder(docFolder); | |
| } | |
| Future<void> verifyFile(File file) async { | |
| String content = file.readAsStringSync(); | |
| List<String> lines = const LineSplitter().convert(content); | |
| List<String> codeLines = []; | |
| bool inCode = false; | |
| for (int i = 0; i < lines.length; i++) { | |
| String line = lines[i]; | |
| if (line == '```dart') { | |
| if (inCode) { | |
| // TODO(brianwilkerson) Report this. | |
| } | |
| inCode = true; | |
| } else if (line == '```') { | |
| if (!inCode) { | |
| // TODO(brianwilkerson) Report this. | |
| } | |
| await verifySnippet(file, codeLines.join('\n')); | |
| codeLines.clear(); | |
| inCode = false; | |
| } else if (inCode) { | |
| codeLines.add(line); | |
| } | |
| } | |
| } | |
| Future<void> verifyFolder(Folder folder) async { | |
| for (Resource child in folder.getChildren()) { | |
| if (child is File) { | |
| if (child.shortName.endsWith('.md')) { | |
| await verifyFile(child); | |
| } | |
| } else if (child is Folder) { | |
| await verifyFolder(child); | |
| } | |
| } | |
| } | |
| Future<void> verifySnippet(File file, String snippet) async { | |
| // TODO(brianwilkerson) When the files outside of 'src' contain only public | |
| // API, write code to compute the list of imports so that new public API | |
| // will automatically be allowed. | |
| String imports = ''' | |
| // @dart = 2.9 | |
| import 'dart:math' as math; | |
| import 'package:analyzer/dart/analysis/analysis_context.dart'; | |
| import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; | |
| import 'package:analyzer/dart/analysis/results.dart'; | |
| import 'package:analyzer/dart/analysis/session.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/dart/element/visitor.dart'; | |
| '''; | |
| provider.setOverlay(snippetPath, | |
| content: ''' | |
| $imports | |
| $snippet | |
| ''', | |
| modificationStamp: 1); | |
| try { | |
| AnalysisContextCollection collection = AnalysisContextCollection( | |
| includedPaths: <String>[snippetDirPath], resourceProvider: provider); | |
| List<AnalysisContext> contexts = collection.contexts; | |
| if (contexts.length != 1) { | |
| fail('The snippets directory contains multiple analysis contexts.'); | |
| } | |
| var results = await contexts[0].currentSession.getErrors2(snippetPath); | |
| if (results is ErrorsResult) { | |
| Iterable<AnalysisError> errors = results.errors.where((error) { | |
| ErrorCode errorCode = error.errorCode; | |
| return errorCode != HintCode.UNUSED_IMPORT && | |
| errorCode != HintCode.UNUSED_LOCAL_VARIABLE; | |
| }); | |
| if (errors.isNotEmpty) { | |
| String filePath = | |
| provider.pathContext.relative(file.path, from: docFolder.path); | |
| if (output.isNotEmpty) { | |
| output.writeln(); | |
| } | |
| output.writeln('Errors in snippet in "$filePath":'); | |
| output.writeln(); | |
| output.writeln(snippet); | |
| output.writeln(); | |
| int importsLength = imports.length + 1; // account for the '\n'. | |
| for (var error in errors) { | |
| writeError(error, importsLength); | |
| } | |
| } | |
| } | |
| } catch (exception, stackTrace) { | |
| if (output.isNotEmpty) { | |
| output.writeln(); | |
| } | |
| output.writeln('Exception while analyzing "$snippet"'); | |
| output.writeln(); | |
| output.writeln(exception); | |
| output.writeln(stackTrace); | |
| } finally { | |
| provider.removeOverlay(snippetPath); | |
| } | |
| } | |
| void writeError(AnalysisError error, int prefixLength) { | |
| output.write(error.errorCode); | |
| output.write(' ('); | |
| output.write(error.offset - prefixLength); | |
| output.write(', '); | |
| output.write(error.length); | |
| output.write(') '); | |
| output.writeln(error.message); | |
| } | |
| } |