mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

flutter analyze was unconditionally using the cached dart-sdk for analysis, and was not running with the built SDK during local engine. This broke when trying to update the analyzer for null safety, since it required us to wait for the dart change to roll into the framework first.
463 lines
14 KiB
Dart
463 lines
14 KiB
Dart
// 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:async';
|
|
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/artifacts.dart';
|
|
import 'package:flutter_tools/src/base/common.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/base/terminal.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/commands/analyze.dart';
|
|
import 'package:flutter_tools/src/runner/flutter_command.dart';
|
|
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
|
|
import 'package:process/process.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
|
|
final Platform _kNoColorTerminalPlatform = FakePlatform(
|
|
stdoutSupportsAnsi: false);
|
|
|
|
void main() {
|
|
String analyzerSeparator;
|
|
FileSystem fileSystem;
|
|
Platform platform;
|
|
BufferLogger logger;
|
|
AnsiTerminal terminal;
|
|
ProcessManager processManager;
|
|
Directory tempDir;
|
|
String projectPath;
|
|
File libMain;
|
|
Artifacts artifacts;
|
|
|
|
Future<void> runCommand({
|
|
FlutterCommand command,
|
|
List<String> arguments,
|
|
List<String> statusTextContains,
|
|
List<String> errorTextContains,
|
|
bool toolExit = false,
|
|
String exitMessageContains,
|
|
}) async {
|
|
try {
|
|
arguments.insert(0, '--flutter-root=${Cache.flutterRoot}');
|
|
await createTestCommandRunner(command).run(arguments);
|
|
expect(toolExit, isFalse, reason: 'Expected ToolExit exception');
|
|
} on ToolExit catch (e) {
|
|
if (!toolExit) {
|
|
testLogger.clear();
|
|
rethrow;
|
|
}
|
|
if (exitMessageContains != null) {
|
|
expect(e.message, contains(exitMessageContains));
|
|
}
|
|
}
|
|
assertContains(logger.statusText, statusTextContains);
|
|
assertContains(logger.errorText, errorTextContains);
|
|
|
|
logger.clear();
|
|
}
|
|
|
|
void _createDotPackages(String projectPath) {
|
|
final StringBuffer flutterRootUri = StringBuffer('file://');
|
|
final String canonicalizedFlutterRootPath = fileSystem.path.canonicalize(Cache.flutterRoot);
|
|
if (platform.isWindows) {
|
|
flutterRootUri
|
|
..write('/')
|
|
..write(canonicalizedFlutterRootPath.replaceAll('\\', '/'));
|
|
} else {
|
|
flutterRootUri.write(canonicalizedFlutterRootPath);
|
|
}
|
|
final String dotPackagesSrc = '''# Generated
|
|
flutter:$flutterRootUri/packages/flutter/lib/
|
|
sky_engine:$flutterRootUri/bin/cache/pkg/sky_engine/lib/
|
|
flutter_project:lib/
|
|
''';
|
|
fileSystem.file(fileSystem.path.join(projectPath, '.packages'))
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(dotPackagesSrc);
|
|
}
|
|
|
|
setUpAll(() {
|
|
Cache.disableLocking();
|
|
Cache.flutterRoot = FlutterCommandRunner.defaultFlutterRoot;
|
|
processManager = const LocalProcessManager();
|
|
platform = const LocalPlatform();
|
|
terminal = AnsiTerminal(platform: platform, stdio: Stdio());
|
|
fileSystem = LocalFileSystem.instance;
|
|
logger = BufferLogger(
|
|
outputPreferences: OutputPreferences.test(),
|
|
terminal: terminal,
|
|
);
|
|
analyzerSeparator = platform.isWindows ? '-' : '•';
|
|
artifacts = CachedArtifacts(
|
|
cache: globals.cache,
|
|
fileSystem: fileSystem,
|
|
platform: platform,
|
|
);
|
|
});
|
|
|
|
setUp(() {
|
|
tempDir = fileSystem.systemTempDirectory.createTempSync(
|
|
'flutter_analyze_once_test_1.',
|
|
).absolute;
|
|
projectPath = fileSystem.path.join(tempDir.path, 'flutter_project');
|
|
fileSystem.file(fileSystem.path.join(projectPath, 'pubspec.yaml'))
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(pubspecYamlSrc);
|
|
_createDotPackages(projectPath);
|
|
libMain = fileSystem.file(fileSystem.path.join(projectPath, 'lib', 'main.dart'))
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(mainDartSrc);
|
|
});
|
|
|
|
tearDown(() {
|
|
tryToDelete(tempDir);
|
|
});
|
|
|
|
// Analyze in the current directory - no arguments
|
|
testUsingContext('working directory', () async {
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
workingDirectory: fileSystem.directory(projectPath),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
terminal: terminal,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub'],
|
|
statusTextContains: <String>['No issues found!'],
|
|
);
|
|
});
|
|
|
|
// Analyze a specific file outside the current directory
|
|
testUsingContext('passing one file throws', () async {
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
platform: platform,
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processManager: processManager,
|
|
terminal: terminal,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub', libMain.path],
|
|
toolExit: true,
|
|
exitMessageContains: 'is not a directory',
|
|
);
|
|
});
|
|
|
|
// Analyze in the current directory - no arguments
|
|
testUsingContext('working directory with errors', () async {
|
|
// Break the code to produce the "Avoid empty else" hint
|
|
// that is upgraded to a warning in package:flutter/analysis_options_user.yaml
|
|
// to assert that we are using the default Flutter analysis options.
|
|
// Also insert a statement that should not trigger a lint here
|
|
// but will trigger a lint later on when an analysis_options.yaml is added.
|
|
String source = await libMain.readAsString();
|
|
source = source.replaceFirst(
|
|
'return MaterialApp(',
|
|
'if (debugPrintRebuildDirtyWidgets) {} else ; return MaterialApp(',
|
|
);
|
|
source = source.replaceFirst(
|
|
'onPressed: _incrementCounter,',
|
|
'// onPressed: _incrementCounter,',
|
|
);
|
|
source = source.replaceFirst(
|
|
'_counter++;',
|
|
'_counter++; throw "an error message";',
|
|
);
|
|
libMain.writeAsStringSync(source);
|
|
|
|
// Analyze in the current directory - no arguments
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
workingDirectory: fileSystem.directory(projectPath),
|
|
platform: platform,
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processManager: processManager,
|
|
terminal: terminal,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub'],
|
|
statusTextContains: <String>[
|
|
'Analyzing',
|
|
'info $analyzerSeparator Avoid empty else statements',
|
|
'info $analyzerSeparator Avoid empty statements',
|
|
'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
|
|
],
|
|
exitMessageContains: '3 issues found.',
|
|
toolExit: true,
|
|
);
|
|
});
|
|
|
|
// Analyze in the current directory - no arguments
|
|
testUsingContext('working directory with local options', () async {
|
|
// Insert an analysis_options.yaml file in the project
|
|
// which will trigger a lint for broken code that was inserted earlier
|
|
final File optionsFile = fileSystem.file(fileSystem.path.join(projectPath, 'analysis_options.yaml'));
|
|
try {
|
|
optionsFile.writeAsStringSync('''
|
|
include: package:flutter/analysis_options_user.yaml
|
|
linter:
|
|
rules:
|
|
- only_throw_errors
|
|
''');
|
|
String source = libMain.readAsStringSync();
|
|
source = source.replaceFirst(
|
|
'onPressed: _incrementCounter,',
|
|
'// onPressed: _incrementCounter,',
|
|
);
|
|
source = source.replaceFirst(
|
|
'_counter++;',
|
|
'_counter++; throw "an error message";',
|
|
);
|
|
libMain.writeAsStringSync(source);
|
|
|
|
// Analyze in the current directory - no arguments
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
workingDirectory: fileSystem.directory(projectPath),
|
|
platform: platform,
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processManager: processManager,
|
|
terminal: terminal,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub'],
|
|
statusTextContains: <String>[
|
|
'Analyzing',
|
|
'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
|
|
'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
|
|
],
|
|
exitMessageContains: '2 issues found.',
|
|
toolExit: true,
|
|
);
|
|
} finally {
|
|
if (optionsFile.existsSync()) {
|
|
optionsFile.deleteSync();
|
|
}
|
|
}
|
|
});
|
|
|
|
testUsingContext('analyze once no duplicate issues', () async {
|
|
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
|
|
_createDotPackages(tempDir.path);
|
|
|
|
try {
|
|
final File foo = fileSystem.file(fileSystem.path.join(tempDir.path, 'foo.dart'));
|
|
foo.writeAsStringSync('''
|
|
import 'bar.dart';
|
|
|
|
void foo() => bar();
|
|
''');
|
|
|
|
final File bar = fileSystem.file(fileSystem.path.join(tempDir.path, 'bar.dart'));
|
|
bar.writeAsStringSync('''
|
|
import 'dart:async'; // unused
|
|
|
|
void bar() {
|
|
}
|
|
''');
|
|
|
|
// Analyze in the current directory - no arguments
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
workingDirectory: tempDir,
|
|
platform: platform,
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processManager: processManager,
|
|
terminal: terminal,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub'],
|
|
statusTextContains: <String>[
|
|
'Analyzing',
|
|
],
|
|
exitMessageContains: '1 issue found.',
|
|
toolExit: true,
|
|
);
|
|
} finally {
|
|
tryToDelete(tempDir);
|
|
}
|
|
});
|
|
|
|
testUsingContext('analyze once returns no issues when source is error-free', () async {
|
|
const String contents = '''
|
|
StringBuffer bar = StringBuffer('baz');
|
|
''';
|
|
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
|
|
_createDotPackages(tempDir.path);
|
|
|
|
tempDir.childFile('main.dart').writeAsStringSync(contents);
|
|
try {
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
workingDirectory: fileSystem.directory(tempDir),
|
|
platform: _kNoColorTerminalPlatform,
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processManager: processManager,
|
|
terminal: terminal,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub'],
|
|
statusTextContains: <String>['No issues found!'],
|
|
);
|
|
} finally {
|
|
tryToDelete(tempDir);
|
|
}
|
|
});
|
|
|
|
testUsingContext('analyze once supports analyzing null-safe code', () async {
|
|
const String contents = '''
|
|
int? bar;
|
|
''';
|
|
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_null_safety.');
|
|
_createDotPackages(tempDir.path);
|
|
|
|
tempDir.childFile('main.dart').writeAsStringSync(contents);
|
|
try {
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
workingDirectory: fileSystem.directory(tempDir),
|
|
platform: _kNoColorTerminalPlatform,
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
processManager: processManager,
|
|
terminal: terminal,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub', '--enable-experiment=non-nullable'],
|
|
statusTextContains: <String>['No issues found!'],
|
|
);
|
|
} finally {
|
|
tryToDelete(tempDir);
|
|
}
|
|
});
|
|
|
|
testUsingContext('analyze once returns no issues for todo comments', () async {
|
|
const String contents = '''
|
|
// TODO(foobar):
|
|
StringBuffer bar = StringBuffer('baz');
|
|
''';
|
|
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.');
|
|
_createDotPackages(tempDir.path);
|
|
|
|
tempDir.childFile('main.dart').writeAsStringSync(contents);
|
|
try {
|
|
await runCommand(
|
|
command: AnalyzeCommand(
|
|
workingDirectory: fileSystem.directory(tempDir),
|
|
platform: _kNoColorTerminalPlatform,
|
|
terminal: terminal,
|
|
processManager: processManager,
|
|
logger: logger,
|
|
fileSystem: fileSystem,
|
|
artifacts: artifacts,
|
|
),
|
|
arguments: <String>['analyze', '--no-pub'],
|
|
statusTextContains: <String>['No issues found!'],
|
|
);
|
|
} finally {
|
|
tryToDelete(tempDir);
|
|
}
|
|
});
|
|
}
|
|
|
|
void assertContains(String text, List<String> patterns) {
|
|
if (patterns == null) {
|
|
expect(text, isEmpty);
|
|
} else {
|
|
for (final String pattern in patterns) {
|
|
expect(text, contains(pattern));
|
|
}
|
|
}
|
|
}
|
|
|
|
const String mainDartSrc = r'''
|
|
import 'package:flutter/material.dart';
|
|
|
|
void main() => runApp(MyApp());
|
|
|
|
class MyApp extends StatelessWidget {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
title: 'Flutter Demo',
|
|
theme: ThemeData(
|
|
primarySwatch: Colors.blue,
|
|
),
|
|
home: MyHomePage(title: 'Flutter Demo Home Page'),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MyHomePage extends StatefulWidget {
|
|
MyHomePage({Key key, this.title}) : super(key: key);
|
|
|
|
final String title;
|
|
|
|
@override
|
|
_MyHomePageState createState() => _MyHomePageState();
|
|
}
|
|
|
|
class _MyHomePageState extends State<MyHomePage> {
|
|
int _counter = 0;
|
|
|
|
void _incrementCounter() {
|
|
setState(() {
|
|
_counter++;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(widget.title),
|
|
),
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
Text(
|
|
'You have pushed the button this many times:',
|
|
),
|
|
Text(
|
|
'$_counter',
|
|
style: Theme.of(context).textTheme.headline4,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: _incrementCounter,
|
|
tooltip: 'Increment',
|
|
child: Icon(Icons.add),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
''';
|
|
|
|
const String pubspecYamlSrc = r'''
|
|
name: flutter_project
|
|
environment:
|
|
sdk: ">=2.1.0 <3.0.0"
|
|
|
|
dependencies:
|
|
flutter:
|
|
sdk: flutter
|
|
''';
|