a36e01d43b
Perhaps there's a good reason not do this, but it seems to work fine for now, and doesn't hide errors. On my computer it brought the test down from 35 seconds to 600 ms. Closes https://github.com/dart-lang/sdk/issues/54183 Bug: https://github.com/dart-lang/sdk/issues/54183 Change-Id: I15351ce71897534a82da1f649d33b0b7b6180f4c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/339440 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Auto-Submit: Parker Lougheed <parlough@gmail.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
190 lines
6.6 KiB
Dart
190 lines
6.6 KiB
Dart
// 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();
|
|
}
|
|
|
|
class SnippetTester {
|
|
final OverlayResourceProvider provider;
|
|
final Folder docFolder;
|
|
final String snippetDirPath;
|
|
final String snippetPath;
|
|
final AnalysisContextCollection collection;
|
|
|
|
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)
|
|
: collection = AnalysisContextCollection(
|
|
resourceProvider: provider, includedPaths: [snippetPath]);
|
|
|
|
/// Return `true` if the given error is a diagnostic produced by a lint that
|
|
/// is allowed to occur in documentation.
|
|
bool isAllowedLint(AnalysisError error) {
|
|
var errorCode = error.errorCode;
|
|
return errorCode is LintCode &&
|
|
errorCode.name == 'non_constant_identifier_names' &&
|
|
error.message.contains("'test_");
|
|
}
|
|
|
|
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')) {
|
|
test('Verify docs in ${child.path}', () async {
|
|
output.clear();
|
|
await verifyFile(child);
|
|
if (output.isNotEmpty) {
|
|
fail(output.toString());
|
|
}
|
|
}, timeout: Timeout.factor(4));
|
|
}
|
|
} 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.
|
|
const String imports = '''
|
|
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';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
Future<void> assertNoErrorsInCode(String s) async {}
|
|
void test(String s, void Function() f) {}
|
|
void group(String s, void Function() f) {}
|
|
''';
|
|
provider.setOverlay(snippetPath,
|
|
content: '''
|
|
$imports
|
|
$snippet
|
|
''',
|
|
modificationStamp: 1);
|
|
try {
|
|
List<AnalysisContext> contexts = collection.contexts;
|
|
if (contexts.length != 1) {
|
|
fail('The snippets directory contains multiple analysis contexts.');
|
|
}
|
|
var context = contexts[0];
|
|
// Mark the snippet as changed since we reuse the same path
|
|
// for each snippet found.
|
|
context.changeFile(snippetPath);
|
|
await context.applyPendingFileChanges();
|
|
var results = await context.currentSession.getErrors(snippetPath);
|
|
if (results is ErrorsResult) {
|
|
Iterable<AnalysisError> errors = results.errors.where((error) {
|
|
ErrorCode errorCode = error.errorCode;
|
|
// TODO(brianwilkerson): .
|
|
return errorCode != WarningCode.UNUSED_IMPORT &&
|
|
errorCode != HintCode.UNUSED_LOCAL_VARIABLE &&
|
|
!isAllowedLint(error);
|
|
});
|
|
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);
|
|
}
|
|
}
|