mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Fix the sample analyzer to analyze dart:ui and make the analyzer null safe (#80742)
This commit is contained in:
parent
a84ac2ec2c
commit
b33c7891c1
@ -7,6 +7,8 @@
|
||||
// To run this, from the root of the Flutter repository:
|
||||
// bin/cache/dart-sdk/bin/dart dev/bots/analyze_sample_code.dart
|
||||
|
||||
// @dart= 2.12
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@ -16,6 +18,7 @@ import 'package:watcher/watcher.dart';
|
||||
|
||||
final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
|
||||
final String _defaultFlutterPackage = path.join(_flutterRoot, 'packages', 'flutter', 'lib');
|
||||
final String _defaultDartUiLocation = path.join(_flutterRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui');
|
||||
final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
|
||||
|
||||
void main(List<String> arguments) {
|
||||
@ -33,6 +36,20 @@ void main(List<String> arguments) {
|
||||
negatable: false,
|
||||
help: 'Print verbose output for the analysis process.',
|
||||
);
|
||||
argParser.addOption(
|
||||
'dart-ui-location',
|
||||
defaultsTo: _defaultDartUiLocation,
|
||||
help: 'A location where the dart:ui dart files are to be found. Defaults to '
|
||||
'the sky_engine directory installed in this flutter repo. This '
|
||||
'is typically the engine/src/flutter/lib/ui directory in an engine dev setup. '
|
||||
'Implies --include-dart-ui.',
|
||||
);
|
||||
argParser.addFlag(
|
||||
'include-dart-ui',
|
||||
defaultsTo: true,
|
||||
negatable: true,
|
||||
help: 'Includes the dart:ui code supplied by the engine in the analysis.',
|
||||
);
|
||||
argParser.addFlag(
|
||||
'help',
|
||||
defaultsTo: false,
|
||||
@ -61,7 +78,20 @@ void main(List<String> arguments) {
|
||||
flutterPackage = Directory(_defaultFlutterPackage);
|
||||
}
|
||||
|
||||
Directory tempDirectory;
|
||||
final bool includeDartUi = parsedArguments.wasParsed('dart-ui-location') || parsedArguments['include-dart-ui'] as bool;
|
||||
late Directory dartUiLocation;
|
||||
if (((parsedArguments['dart-ui-location'] ?? '') as String).isNotEmpty) {
|
||||
dartUiLocation = Directory(
|
||||
path.absolute(parsedArguments['dart-ui-location'] as String));
|
||||
} else {
|
||||
dartUiLocation = Directory(_defaultDartUiLocation);
|
||||
}
|
||||
if (!dartUiLocation.existsSync()) {
|
||||
stderr.writeln('Unable to find dart:ui directory ${dartUiLocation.path}');
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
Directory? tempDirectory;
|
||||
if (parsedArguments.wasParsed('temp')) {
|
||||
final String tempArg = parsedArguments['temp'] as String;
|
||||
tempDirectory = Directory(path.join(Directory.systemTemp.absolute.path, path.basename(tempArg)));
|
||||
@ -78,13 +108,19 @@ void main(List<String> arguments) {
|
||||
}
|
||||
|
||||
if (parsedArguments['interactive'] != null) {
|
||||
_runInteractive(tempDirectory, flutterPackage, parsedArguments['interactive'] as String);
|
||||
_runInteractive(
|
||||
tempDir: tempDirectory,
|
||||
flutterPackage: flutterPackage,
|
||||
filePath: parsedArguments['interactive'] as String,
|
||||
dartUiLocation: includeDartUi ? dartUiLocation : null,
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
exitCode = SampleChecker(
|
||||
flutterPackage,
|
||||
tempDirectory: tempDirectory,
|
||||
verbose: parsedArguments['verbose'] as bool,
|
||||
dartUiLocation: includeDartUi ? dartUiLocation : null,
|
||||
).checkSamples();
|
||||
} on SampleCheckerException catch (e) {
|
||||
stderr.write(e);
|
||||
@ -96,8 +132,8 @@ void main(List<String> arguments) {
|
||||
class SampleCheckerException implements Exception {
|
||||
SampleCheckerException(this.message, {this.file, this.line});
|
||||
final String message;
|
||||
final String file;
|
||||
final int line;
|
||||
final String? file;
|
||||
final int? line;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -126,11 +162,30 @@ class SampleCheckerException implements Exception {
|
||||
/// don't necessarily match. It does, however, print the source of the
|
||||
/// problematic line.
|
||||
class SampleChecker {
|
||||
SampleChecker(this._flutterPackage, {Directory tempDirectory, this.verbose = false})
|
||||
: _tempDirectory = tempDirectory,
|
||||
_keepTmp = tempDirectory != null {
|
||||
_tempDirectory ??= Directory.systemTemp.createTempSync('flutter_analyze_sample_code.');
|
||||
}
|
||||
/// Creates a [SampleChecker].
|
||||
///
|
||||
/// The positional argument is the path to the the package directory for the
|
||||
/// flutter package within the Flutter root dir.
|
||||
///
|
||||
/// The optional `tempDirectory` argument supplies the location for the
|
||||
/// temporary files to be written and analyzed. If not supplied, it defaults
|
||||
/// to a system generated temp directory.
|
||||
///
|
||||
/// The optional `verbose` argument indicates whether or not status output
|
||||
/// should be emitted while doing the check.
|
||||
///
|
||||
/// The optional `dartUiLocation` argument indicates the location of the
|
||||
/// `dart:ui` code to be analyzed along with the framework code. If not
|
||||
/// supplied, the default location of the `dart:ui` code in the Flutter
|
||||
/// repository is used (i.e. "<flutter repo>/bin/cache/pkg/sky_engine/lib/ui").
|
||||
SampleChecker(
|
||||
this._flutterPackage, {
|
||||
Directory? tempDirectory,
|
||||
this.verbose = false,
|
||||
Directory? dartUiLocation,
|
||||
}) : _tempDirectory = tempDirectory ?? Directory.systemTemp.createTempSync('flutter_analyze_sample_code.'),
|
||||
_keepTmp = tempDirectory != null,
|
||||
_dartUiLocation = dartUiLocation;
|
||||
|
||||
/// The prefix of each comment line
|
||||
static const String _dartDocPrefix = '///';
|
||||
@ -166,11 +221,18 @@ class SampleChecker {
|
||||
|
||||
/// The temporary directory where all output is written. This will be deleted
|
||||
/// automatically if there are no errors.
|
||||
Directory _tempDirectory;
|
||||
final Directory _tempDirectory;
|
||||
|
||||
/// The package directory for the flutter package within the flutter root dir.
|
||||
final Directory _flutterPackage;
|
||||
|
||||
/// The directory for the dart:ui code to be analyzed with the flutter code.
|
||||
///
|
||||
/// If this is null, then no dart:ui code is included in the analysis. It
|
||||
/// defaults to the location inside of the flutter bin/cache directory that
|
||||
/// contains the dart:ui code supplied by the engine.
|
||||
final Directory? _dartUiLocation;
|
||||
|
||||
/// A serial number so that we can create unique expression names when we
|
||||
/// generate them.
|
||||
int _expressionId = 0;
|
||||
@ -180,7 +242,7 @@ class SampleChecker {
|
||||
|
||||
// Once the snippets tool has been precompiled by Dart, this contains the AOT
|
||||
// snapshot.
|
||||
String _snippetsSnapshotPath;
|
||||
String? _snippetsSnapshotPath;
|
||||
|
||||
/// Finds the location of the snippets script.
|
||||
String get _snippetsExecutable {
|
||||
@ -219,7 +281,7 @@ class SampleChecker {
|
||||
].map<Line>((String code) => Line(code)).toList();
|
||||
}
|
||||
|
||||
List<Line> _headers;
|
||||
List<Line>? _headers;
|
||||
|
||||
/// Checks all the samples in the Dart files in [_flutterPackage] for errors.
|
||||
int checkSamples() {
|
||||
@ -228,12 +290,19 @@ class SampleChecker {
|
||||
try {
|
||||
final Map<String, Section> sections = <String, Section>{};
|
||||
final Map<String, Sample> snippets = <String, Sample>{};
|
||||
_extractSamples(_listDartFiles(_flutterPackage, recursive: true), sectionMap: sections, sampleMap: snippets);
|
||||
if (_dartUiLocation != null && !_dartUiLocation!.existsSync()) {
|
||||
stderr.writeln('Unable to analyze engine dart samples at ${_dartUiLocation!.path}.');
|
||||
}
|
||||
final List<File> filesToAnalyze = <File>[
|
||||
..._listDartFiles(_flutterPackage, recursive: true),
|
||||
if (_dartUiLocation != null && _dartUiLocation!.existsSync()) ... _listDartFiles(_dartUiLocation!, recursive: true),
|
||||
];
|
||||
_extractSamples(filesToAnalyze, sectionMap: sections, sampleMap: snippets);
|
||||
errors = _analyze(_tempDirectory, sections, snippets);
|
||||
} finally {
|
||||
if (errors.isNotEmpty) {
|
||||
for (final String filePath in errors.keys) {
|
||||
errors[filePath].forEach(stderr.writeln);
|
||||
errors[filePath]!.forEach(stderr.writeln);
|
||||
}
|
||||
stderr.writeln('\nFound ${errors.length} sample code errors.');
|
||||
}
|
||||
@ -248,7 +317,7 @@ class SampleChecker {
|
||||
}
|
||||
// If we made a snapshot, remove it (so as not to clutter up the tree).
|
||||
if (_snippetsSnapshotPath != null) {
|
||||
final File snapshot = File(_snippetsSnapshotPath);
|
||||
final File snapshot = File(_snippetsSnapshotPath!);
|
||||
if (snapshot.existsSync()) {
|
||||
snapshot.deleteSync();
|
||||
}
|
||||
@ -285,7 +354,7 @@ class SampleChecker {
|
||||
} else {
|
||||
return Process.runSync(
|
||||
_dartExecutable,
|
||||
<String>[path.canonicalize(_snippetsSnapshotPath), ...args],
|
||||
<String>[path.canonicalize(_snippetsSnapshotPath!), ...args],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
}
|
||||
@ -307,7 +376,7 @@ class SampleChecker {
|
||||
...sample.args,
|
||||
];
|
||||
if (verbose)
|
||||
print('Generating sample for ${sample.start?.filename}:${sample.start?.line}');
|
||||
print('Generating sample for ${sample.start.filename}:${sample.start.line}');
|
||||
final ProcessResult process = _runSnippetsScript(args);
|
||||
if (verbose)
|
||||
stderr.write('${process.stderr}');
|
||||
@ -324,14 +393,19 @@ class SampleChecker {
|
||||
|
||||
/// Extracts the samples from the Dart files in [files], writes them
|
||||
/// to disk, and adds them to the appropriate [sectionMap] or [sampleMap].
|
||||
void _extractSamples(List<File> files, {Map<String, Section> sectionMap, Map<String, Sample> sampleMap, bool silent = false}) {
|
||||
void _extractSamples(
|
||||
List<File> files, {
|
||||
required Map<String, Section> sectionMap,
|
||||
required Map<String, Sample> sampleMap,
|
||||
bool silent = false,
|
||||
}) {
|
||||
final List<Section> sections = <Section>[];
|
||||
final List<Sample> samples = <Sample>[];
|
||||
int dartpadCount = 0;
|
||||
int sampleCount = 0;
|
||||
|
||||
for (final File file in files) {
|
||||
final String relativeFilePath = path.relative(file.path, from: _flutterPackage.path);
|
||||
final String relativeFilePath = path.relative(file.path, from: _flutterRoot);
|
||||
final List<String> sampleLines = file.readAsLinesSync();
|
||||
final List<Section> preambleSections = <Section>[];
|
||||
// Whether or not we're in the file-wide preamble section ("Examples can assume").
|
||||
@ -342,11 +416,11 @@ class SampleChecker {
|
||||
bool inSnippet = false;
|
||||
// Whether or not we're in a '```dart' segment.
|
||||
bool inDart = false;
|
||||
String dartVersionOverride;
|
||||
String? dartVersionOverride;
|
||||
int lineNumber = 0;
|
||||
final List<String> block = <String>[];
|
||||
List<String> snippetArgs = <String>[];
|
||||
Line startLine;
|
||||
late Line startLine;
|
||||
for (final String line in sampleLines) {
|
||||
lineNumber += 1;
|
||||
final String trimmedLine = line.trim();
|
||||
@ -425,7 +499,7 @@ class SampleChecker {
|
||||
}
|
||||
}
|
||||
if (!inSampleSection) {
|
||||
final Match sampleMatch = _dartDocSampleBeginRegex.firstMatch(trimmedLine);
|
||||
final RegExpMatch? sampleMatch = _dartDocSampleBeginRegex.firstMatch(trimmedLine);
|
||||
if (line == '// Examples can assume:') {
|
||||
assert(block.isEmpty);
|
||||
startLine = Line('', filename: relativeFilePath, line: lineNumber + 1, indent: 3);
|
||||
@ -447,7 +521,7 @@ class SampleChecker {
|
||||
);
|
||||
if (sampleMatch[2] != null) {
|
||||
// There are arguments to the snippet tool to keep track of.
|
||||
snippetArgs = _splitUpQuotedArgs(sampleMatch[2]).toList();
|
||||
snippetArgs = _splitUpQuotedArgs(sampleMatch[2]!).toList();
|
||||
} else {
|
||||
snippetArgs = <String>[];
|
||||
}
|
||||
@ -507,7 +581,7 @@ class SampleChecker {
|
||||
// by an equals sign), add a "--" in front so that they parse as options.
|
||||
return matches.map<String>((Match match) {
|
||||
String option = '';
|
||||
if (match[1] != null && !match[1].startsWith('-')) {
|
||||
if (match[1] != null && !match[1]!.startsWith('-')) {
|
||||
option = '--';
|
||||
}
|
||||
if (match[2] != null) {
|
||||
@ -543,7 +617,7 @@ dependencies:
|
||||
final String sectionId = _createNameFromSource('snippet', section.start.filename, section.start.line);
|
||||
final File outputFile = File(path.join(_tempDirectory.path, '$sectionId.dart'))..createSync(recursive: true);
|
||||
final List<Line> mainContents = <Line>[
|
||||
if (section.dartVersionOverride != null) Line(section.dartVersionOverride) else const Line(''),
|
||||
if (section.dartVersionOverride != null) Line(section.dartVersionOverride!) else const Line(''),
|
||||
...headers,
|
||||
const Line(''),
|
||||
Line('// From: ${section.start.filename}:${section.start.line}'),
|
||||
@ -554,7 +628,7 @@ dependencies:
|
||||
}
|
||||
|
||||
/// Invokes the analyzer on the given [directory] and returns the stdout.
|
||||
List<String> _runAnalyzer(Directory directory, {bool silent}) {
|
||||
List<String> _runAnalyzer(Directory directory, {bool silent = true}) {
|
||||
if (!silent)
|
||||
print('Starting analysis of code samples.');
|
||||
_createConfigurationFiles(directory);
|
||||
@ -603,7 +677,7 @@ dependencies:
|
||||
final Map<String, List<AnalysisError>> analysisErrors = <String, List<AnalysisError>>{};
|
||||
void addAnalysisError(File file, AnalysisError error) {
|
||||
if (analysisErrors.containsKey(file.path)) {
|
||||
analysisErrors[file.path].add(error);
|
||||
analysisErrors[file.path]!.add(error);
|
||||
} else {
|
||||
analysisErrors[file.path] = <AnalysisError>[error];
|
||||
}
|
||||
@ -621,21 +695,21 @@ dependencies:
|
||||
bool unknownAnalyzerErrors = false;
|
||||
final int headerLength = headers.length + 3;
|
||||
for (final String error in errors) {
|
||||
final RegExpMatch match = errorPattern.firstMatch(error);
|
||||
final RegExpMatch? match = errorPattern.firstMatch(error);
|
||||
if (match == null) {
|
||||
stderr.writeln('Analyzer output: $error');
|
||||
unknownAnalyzerErrors = true;
|
||||
continue;
|
||||
}
|
||||
final String type = match.namedGroup('type');
|
||||
final String message = match.namedGroup('description');
|
||||
final String type = match.namedGroup('type')!;
|
||||
final String message = match.namedGroup('description')!;
|
||||
final File file = File(path.join(_tempDirectory.path, match.namedGroup('file')));
|
||||
final List<String> fileContents = file.readAsLinesSync();
|
||||
final bool isSnippet = path.basename(file.path).startsWith('snippet.');
|
||||
final bool isSample = path.basename(file.path).startsWith('sample.');
|
||||
final String line = match.namedGroup('line');
|
||||
final String column = match.namedGroup('column');
|
||||
final String errorCode = match.namedGroup('code');
|
||||
final String line = match.namedGroup('line')!;
|
||||
final String column = match.namedGroup('column')!;
|
||||
final String errorCode = match.namedGroup('code')!;
|
||||
final int lineNumber = int.parse(line, radix: 10) - (isSnippet ? headerLength : 0);
|
||||
final int columnNumber = int.parse(column, radix: 10);
|
||||
|
||||
@ -692,7 +766,7 @@ dependencies:
|
||||
throw SampleCheckerException('Failed to parse error message: $error', file: file.path, line: lineNumber);
|
||||
}
|
||||
|
||||
final Section actualSection = sections[file.path];
|
||||
final Section actualSection = sections[file.path]!;
|
||||
if (actualSection == null) {
|
||||
throw SampleCheckerException(
|
||||
"Unknown section for ${file.path}. Maybe the temporary directory wasn't empty?",
|
||||
@ -702,10 +776,10 @@ dependencies:
|
||||
}
|
||||
final Line actualLine = actualSection.code[lineNumber - 1];
|
||||
|
||||
if (actualLine?.filename == null) {
|
||||
if (actualLine.filename == null) {
|
||||
if (errorCode == 'missing_identifier' && lineNumber > 1) {
|
||||
if (fileContents[lineNumber - 2].endsWith(',')) {
|
||||
final Line actualLine = sections[file.path].code[lineNumber - 2];
|
||||
final Line actualLine = sections[file.path]!.code[lineNumber - 2];
|
||||
addAnalysisError(
|
||||
file,
|
||||
AnalysisError(
|
||||
@ -779,7 +853,7 @@ dependencies:
|
||||
} else {
|
||||
final List<String> buffer = <String>[];
|
||||
int subblocks = 0;
|
||||
Line subline;
|
||||
Line? subline;
|
||||
final List<Section> subsections = <Section>[];
|
||||
for (int index = 0; index < block.length; index += 1) {
|
||||
// Each section of the dart code that is either split by a blank line, or with '// ...' is
|
||||
@ -821,7 +895,7 @@ dependencies:
|
||||
|
||||
/// A class to represent a line of input code.
|
||||
class Line {
|
||||
const Line(this.code, {this.filename, this.line, this.indent});
|
||||
const Line(this.code, {this.filename = 'unknown', this.line = -1, this.indent = 0});
|
||||
final String filename;
|
||||
final int line;
|
||||
final int indent;
|
||||
@ -883,10 +957,10 @@ class Section {
|
||||
}
|
||||
Line get start => code.firstWhere((Line line) => line.filename != null);
|
||||
final List<Line> code;
|
||||
final String dartVersionOverride;
|
||||
final String? dartVersionOverride;
|
||||
|
||||
Section copyWith({String dartVersionOverride}) {
|
||||
return Section(code, dartVersionOverride: dartVersionOverride);
|
||||
Section copyWith({String? dartVersionOverride}) {
|
||||
return Section(code, dartVersionOverride: dartVersionOverride ?? this.dartVersionOverride);
|
||||
}
|
||||
}
|
||||
|
||||
@ -895,10 +969,14 @@ class Section {
|
||||
/// regular snippets, because they must be injected into templates in order to be
|
||||
/// analyzed.
|
||||
class Sample {
|
||||
Sample({this.start, List<String> input, List<String> args, this.serial}) {
|
||||
this.input = input.toList();
|
||||
this.args = args.toList();
|
||||
}
|
||||
Sample({
|
||||
required this.start,
|
||||
required List<String> input,
|
||||
required List<String> args,
|
||||
required this.serial,
|
||||
}) : input = input.toList(),
|
||||
args = args.toList(),
|
||||
contents = <String>[];
|
||||
final Line start;
|
||||
final int serial;
|
||||
List<String> input;
|
||||
@ -936,16 +1014,16 @@ class AnalysisError {
|
||||
final int column;
|
||||
final String message;
|
||||
final String errorCode;
|
||||
final Line source;
|
||||
final Sample sample;
|
||||
final Line? source;
|
||||
final Sample? sample;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (source != null) {
|
||||
return '${source.toStringWithColumn(column)}\n>>> $type: $message ($errorCode)';
|
||||
return '${source!.toStringWithColumn(column)}\n>>> $type: $message ($errorCode)';
|
||||
} else if (sample != null) {
|
||||
return 'In sample starting at '
|
||||
'${sample.start.filename}:${sample.start.line}:${sample.contents[line - 1]}\n'
|
||||
'${sample!.start.filename}:${sample!.start.line}:${sample!.contents[line - 1]}\n'
|
||||
'>>> $type: $message ($errorCode)';
|
||||
} else {
|
||||
return '<source unknown>:$line:$column\n>>> $type: $message ($errorCode)';
|
||||
@ -953,21 +1031,28 @@ class AnalysisError {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runInteractive(Directory tempDir, Directory flutterPackage, String filePath) async {
|
||||
Future<void> _runInteractive({
|
||||
required Directory? tempDir,
|
||||
required Directory flutterPackage,
|
||||
required String filePath,
|
||||
required Directory? dartUiLocation,
|
||||
}) async {
|
||||
filePath = path.isAbsolute(filePath) ? filePath : path.join(path.current, filePath);
|
||||
final File file = File(filePath);
|
||||
if (!file.existsSync()) {
|
||||
throw 'Path ${file.absolute.path} does not exist.';
|
||||
throw 'Path ${file.absolute.path} does not exist ($filePath).';
|
||||
}
|
||||
if (!path.isWithin(_flutterRoot, file.absolute.path)) {
|
||||
throw 'Path ${file.absolute.path} is not within the flutter root: $_flutterRoot';
|
||||
if (!path.isWithin(_flutterRoot, file.absolute.path) &&
|
||||
(dartUiLocation == null || !path.isWithin(dartUiLocation.path, file.absolute.path))) {
|
||||
throw 'Path ${file.absolute.path} is not within the flutter root: '
|
||||
'$_flutterRoot${dartUiLocation != null ? ' or the dart:ui location: $dartUiLocation' : ''}';
|
||||
}
|
||||
|
||||
if (tempDir == null) {
|
||||
tempDir = Directory.systemTemp.createTempSync('flutter_analyze_sample_code.');
|
||||
ProcessSignal.sigint.watch().listen((_) {
|
||||
print('Deleting temp files...');
|
||||
tempDir.deleteSync(recursive: true);
|
||||
tempDir!.deleteSync(recursive: true);
|
||||
exit(0);
|
||||
});
|
||||
print('Using temp dir ${tempDir.path}');
|
||||
@ -982,7 +1067,7 @@ Future<void> _runInteractive(Directory tempDir, Directory flutterPackage, String
|
||||
stderr.writeln('\u001B[2J\u001B[H'); // Clears the old results from the terminal.
|
||||
if (errors.isNotEmpty) {
|
||||
for (final String filePath in errors.keys) {
|
||||
errors[filePath].forEach(stderr.writeln);
|
||||
errors[filePath]!.forEach(stderr.writeln);
|
||||
}
|
||||
stderr.writeln('\nFound ${errors.length} errors.');
|
||||
} else {
|
||||
@ -1012,10 +1097,9 @@ Future<void> _runInteractive(Directory tempDir, Directory flutterPackage, String
|
||||
case 'q':
|
||||
print('Exiting...');
|
||||
exit(0);
|
||||
break;
|
||||
case 'r':
|
||||
print('Deleting temp files...');
|
||||
tempDir.deleteSync(recursive: true);
|
||||
tempDir!.deleteSync(recursive: true);
|
||||
rerun();
|
||||
break;
|
||||
}
|
||||
|
33
dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart
Normal file
33
dev/bots/test/analyze-sample-code-test-dart-ui/ui.dart
Normal file
@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.12
|
||||
|
||||
// This is a dummy dart:ui package for the sample code analyzer tests to use.
|
||||
|
||||
library dart.ui;
|
||||
|
||||
/// Annotation used by Flutter's Dart compiler to indicate that an
|
||||
/// [Object.toString] override should not be replaced with a supercall.
|
||||
///
|
||||
/// {@tool sample}
|
||||
/// A sample if using keepToString to prevent replacement by a supercall.
|
||||
///
|
||||
/// ```dart
|
||||
/// class MyStringBuffer {
|
||||
/// StringBuffer _buffer = StringBuffer();
|
||||
///
|
||||
/// @keepToString
|
||||
/// @override
|
||||
/// String toString() {
|
||||
/// return _buffer.toString();
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
const _KeepToString keepToString = _KeepToString();
|
||||
|
||||
class _KeepToString {
|
||||
const _KeepToString();
|
||||
}
|
@ -7,35 +7,42 @@ import 'dart:io';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
test('analyze_sample_code', () {
|
||||
// These tests don't run on Windows because the sample analyzer doesn't
|
||||
// support Windows as a platform, since it is only run on Linux in the
|
||||
// continuous integration tests.
|
||||
if (Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
test('analyze_sample_code smoke test', () {
|
||||
final ProcessResult process = Process.runSync(
|
||||
'../../bin/cache/dart-sdk/bin/dart',
|
||||
<String>['analyze_sample_code.dart', 'test/analyze-sample-code-test-input'],
|
||||
<String>['analyze_sample_code.dart', '--no-include-dart-ui', 'test/analyze-sample-code-test-input'],
|
||||
);
|
||||
final List<String> stdoutLines = process.stdout.toString().split('\n');
|
||||
final List<String> stderrLines = process.stderr.toString().split('\n')
|
||||
..removeWhere((String line) => line.startsWith('Analyzer output:') || line.startsWith('Building flutter tool...'));
|
||||
expect(process.exitCode, isNot(equals(0)));
|
||||
expect(stderrLines, <String>[
|
||||
'In sample starting at known_broken_documentation.dart:117:bool? _visible = true;',
|
||||
'In sample starting at dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:117:bool? _visible = true;',
|
||||
'>>> info: Use late for private members with non-nullable type (use_late_for_private_fields_and_variables)',
|
||||
'In sample starting at known_broken_documentation.dart:117: child: Text(title),',
|
||||
'In sample starting at dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:117: child: Text(title),',
|
||||
'>>> error: The final variable \'title\' can\'t be read because it is potentially unassigned at this point (read_potentially_unassigned_final)',
|
||||
'known_broken_documentation.dart:30:9: new Opacity(',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:30:9: new Opacity(',
|
||||
'>>> info: Unnecessary new keyword (unnecessary_new)',
|
||||
'known_broken_documentation.dart:62:9: new Opacity(',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:62:9: new Opacity(',
|
||||
'>>> info: Unnecessary new keyword (unnecessary_new)',
|
||||
'known_broken_documentation.dart:95:9: const text0 = Text(\'Poor wandering ones!\');',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:95:9: const text0 = Text(\'Poor wandering ones!\');',
|
||||
'>>> info: Specify type annotations (always_specify_types)',
|
||||
'known_broken_documentation.dart:103:9: const text1 = _Text(\'Poor wandering ones!\');',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:103:9: const text1 = _Text(\'Poor wandering ones!\');',
|
||||
'>>> info: Specify type annotations (always_specify_types)',
|
||||
'known_broken_documentation.dart:111:9: final String? bar = \'Hello\';',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:111:9: final String? bar = \'Hello\';',
|
||||
'>>> info: Prefer const over final for declarations (prefer_const_declarations)',
|
||||
'known_broken_documentation.dart:111:23: final String? bar = \'Hello\';',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:111:23: final String? bar = \'Hello\';',
|
||||
'>>> info: Use a non-nullable type for a final variable initialized with a non-nullable value (unnecessary_nullable_for_final_variable_declarations)',
|
||||
'known_broken_documentation.dart:112:9: final int foo = null;',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:112:9: final int foo = null;',
|
||||
'>>> info: Prefer const over final for declarations (prefer_const_declarations)',
|
||||
'known_broken_documentation.dart:112:25: final int foo = null;',
|
||||
'dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart:112:25: final int foo = null;',
|
||||
'>>> error: A value of type \'Null\' can\'t be assigned to a variable of type \'int\' (invalid_assignment)',
|
||||
'',
|
||||
'Found 2 sample code errors.',
|
||||
@ -46,5 +53,23 @@ void main() {
|
||||
'Starting analysis of code samples.',
|
||||
'',
|
||||
]);
|
||||
}, skip: Platform.isWindows);
|
||||
});
|
||||
test('Analyzes dart:ui code', () {
|
||||
final ProcessResult process = Process.runSync(
|
||||
'../../bin/cache/dart-sdk/bin/dart',
|
||||
<String>[
|
||||
'analyze_sample_code.dart',
|
||||
'--dart-ui-location',
|
||||
'test/analyze-sample-code-test-dart-ui',
|
||||
'test/analyze-sample-code-test-input',
|
||||
],
|
||||
);
|
||||
final List<String> stdoutLines = process.stdout.toString().split('\n');
|
||||
expect(process.exitCode, isNot(equals(0)));
|
||||
expect(stdoutLines, equals(<String>[
|
||||
// There is one sample code section in the test's dummy dart:ui code.
|
||||
'Found 8 snippet code blocks, 1 sample code sections, and 2 dartpad sections.',
|
||||
'',
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
@ -179,7 +179,9 @@ class SnippetGenerator {
|
||||
description.add(line);
|
||||
} else {
|
||||
assert(language != null);
|
||||
components.last.contents.add(line);
|
||||
if (components.isNotEmpty) {
|
||||
components.last.contents.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return <_ComponentTuple>[
|
||||
|
Loading…
Reference in New Issue
Block a user