Unverified Commit ee43de04 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] support enable-experiment in flutter analyze (#54613)

parent 9cb9bfbd
...@@ -168,7 +168,7 @@ class AnsiTerminal implements Terminal { ...@@ -168,7 +168,7 @@ class AnsiTerminal implements Terminal {
static String colorCode(TerminalColor color) => _colorMap[color]; static String colorCode(TerminalColor color) => _colorMap[color];
@override @override
bool get supportsColor => _platform.stdoutSupportsAnsi ?? false; bool get supportsColor => _platform?.stdoutSupportsAnsi ?? false;
// Assume unicode emojis are supported when not on Windows. // Assume unicode emojis are supported when not on Windows.
// If we are on Windows, unicode emojis are supported in Windows Terminal, // If we are on Windows, unicode emojis are supported in Windows Terminal,
......
...@@ -30,6 +30,7 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -30,6 +30,7 @@ class AnalyzeCommand extends FlutterCommand {
_logger = logger, _logger = logger,
_terminal = terminal, _terminal = terminal,
_platform = platform { _platform = platform {
addEnableExperimentation(verbose: verboseHelp);
argParser.addFlag('flutter-repo', argParser.addFlag('flutter-repo',
negatable: false, negatable: false,
help: 'Include all the examples and tests from the Flutter repository.', help: 'Include all the examples and tests from the Flutter repository.',
...@@ -118,6 +119,7 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -118,6 +119,7 @@ class AnalyzeCommand extends FlutterCommand {
platform: _platform, platform: _platform,
processManager: _processManager, processManager: _processManager,
terminal: _terminal, terminal: _terminal,
experiments: stringsArg('enable-experiment'),
).analyze(); ).analyze();
} else { } else {
await AnalyzeOnce( await AnalyzeOnce(
...@@ -132,6 +134,7 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -132,6 +134,7 @@ class AnalyzeCommand extends FlutterCommand {
platform: _platform, platform: _platform,
processManager: _processManager, processManager: _processManager,
terminal: _terminal, terminal: _terminal,
experiments: stringsArg('enable-experiment'),
).analyze(); ).analyze();
} }
return FlutterCommandResult.success(); return FlutterCommandResult.success();
......
...@@ -28,6 +28,7 @@ abstract class AnalyzeBase { ...@@ -28,6 +28,7 @@ abstract class AnalyzeBase {
@required this.platform, @required this.platform,
@required this.processManager, @required this.processManager,
@required this.terminal, @required this.terminal,
@required this.experiments,
}); });
/// The parsed argument results for execution. /// The parsed argument results for execution.
...@@ -46,6 +47,8 @@ abstract class AnalyzeBase { ...@@ -46,6 +47,8 @@ abstract class AnalyzeBase {
final Platform platform; final Platform platform;
@protected @protected
final AnsiTerminal terminal; final AnsiTerminal terminal;
@protected
final List<String> experiments;
/// Called by [AnalyzeCommand] to start the analysis process. /// Called by [AnalyzeCommand] to start the analysis process.
Future<void> analyze(); Future<void> analyze();
......
...@@ -27,6 +27,7 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -27,6 +27,7 @@ class AnalyzeContinuously extends AnalyzeBase {
@required AnsiTerminal terminal, @required AnsiTerminal terminal,
@required Platform platform, @required Platform platform,
@required ProcessManager processManager, @required ProcessManager processManager,
@required List<String> experiments,
}) : super( }) : super(
argResults, argResults,
repoPackages: repoPackages, repoPackages: repoPackages,
...@@ -36,6 +37,7 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -36,6 +37,7 @@ class AnalyzeContinuously extends AnalyzeBase {
platform: platform, platform: platform,
terminal: terminal, terminal: terminal,
processManager: processManager, processManager: processManager,
experiments: experiments,
); );
String analysisTarget; String analysisTarget;
...@@ -74,6 +76,7 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -74,6 +76,7 @@ class AnalyzeContinuously extends AnalyzeBase {
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
terminal: terminal, terminal: terminal,
experiments: experiments,
); );
server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing)); server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing));
server.onErrors.listen(_handleAnalysisErrors); server.onErrors.listen(_handleAnalysisErrors);
......
...@@ -31,6 +31,7 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -31,6 +31,7 @@ class AnalyzeOnce extends AnalyzeBase {
@required Platform platform, @required Platform platform,
@required ProcessManager processManager, @required ProcessManager processManager,
@required AnsiTerminal terminal, @required AnsiTerminal terminal,
@required List<String> experiments,
this.workingDirectory, this.workingDirectory,
}) : super( }) : super(
argResults, argResults,
...@@ -41,6 +42,7 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -41,6 +42,7 @@ class AnalyzeOnce extends AnalyzeBase {
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
terminal: terminal, terminal: terminal,
experiments: experiments,
); );
/// The working directory for testing analysis using dartanalyzer. /// The working directory for testing analysis using dartanalyzer.
...@@ -98,6 +100,7 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -98,6 +100,7 @@ class AnalyzeOnce extends AnalyzeBase {
logger: logger, logger: logger,
processManager: processManager, processManager: processManager,
terminal: terminal, terminal: terminal,
experiments: experiments,
); );
StreamSubscription<bool> subscription; StreamSubscription<bool> subscription;
......
...@@ -24,12 +24,14 @@ class AnalysisServer { ...@@ -24,12 +24,14 @@ class AnalysisServer {
@required ProcessManager processManager, @required ProcessManager processManager,
@required Logger logger, @required Logger logger,
@required Platform platform, @required Platform platform,
@required AnsiTerminal terminal, @required Terminal terminal,
@required List<String> experiments,
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_processManager = processManager, _processManager = processManager,
_logger = logger, _logger = logger,
_platform = platform, _platform = platform,
_terminal = terminal; _terminal = terminal,
_experiments = experiments;
final String sdkPath; final String sdkPath;
final List<String> directories; final List<String> directories;
...@@ -37,7 +39,8 @@ class AnalysisServer { ...@@ -37,7 +39,8 @@ class AnalysisServer {
final ProcessManager _processManager; final ProcessManager _processManager;
final Logger _logger; final Logger _logger;
final Platform _platform; final Platform _platform;
final AnsiTerminal _terminal; final Terminal _terminal;
final List<String> _experiments;
Process _process; Process _process;
final StreamController<bool> _analyzingController = final StreamController<bool> _analyzingController =
...@@ -49,11 +52,20 @@ class AnalysisServer { ...@@ -49,11 +52,20 @@ class AnalysisServer {
int _id = 0; int _id = 0;
Future<void> start() async { Future<void> start() async {
final String snapshot = final String snapshot = _fileSystem.path.join(
_fileSystem.path.join(sdkPath, 'bin/snapshots/analysis_server.dart.snapshot'); sdkPath,
'bin',
'snapshots',
'analysis_server.dart.snapshot',
);
final List<String> command = <String>[ final List<String> command = <String>[
_fileSystem.path.join(sdkPath, 'bin', 'dart'), _fileSystem.path.join(sdkPath, 'bin', 'dart'),
snapshot, snapshot,
for (String experiment in _experiments)
...<String>[
'--enable-experiment',
experiment,
],
'--disable-server-feature-completion', '--disable-server-feature-completion',
'--disable-server-feature-search', '--disable-server-feature-search',
'--sdk', '--sdk',
...@@ -181,14 +193,14 @@ enum _AnalysisSeverity { ...@@ -181,14 +193,14 @@ enum _AnalysisSeverity {
class AnalysisError implements Comparable<AnalysisError> { class AnalysisError implements Comparable<AnalysisError> {
AnalysisError(this.json, { AnalysisError(this.json, {
@required Platform platform, @required Platform platform,
@required AnsiTerminal terminal, @required Terminal terminal,
@required FileSystem fileSystem, @required FileSystem fileSystem,
}) : _platform = platform, }) : _platform = platform,
_terminal = terminal, _terminal = terminal,
_fileSystem = fileSystem; _fileSystem = fileSystem;
final Platform _platform; final Platform _platform;
final AnsiTerminal _terminal; final Terminal _terminal;
final FileSystem _fileSystem; final FileSystem _fileSystem;
static final Map<String, _AnalysisSeverity> _severityMap = <String, _AnalysisSeverity>{ static final Map<String, _AnalysisSeverity> _severityMap = <String, _AnalysisSeverity>{
......
...@@ -462,6 +462,17 @@ abstract class FlutterCommand extends Command<void> { ...@@ -462,6 +462,17 @@ abstract class FlutterCommand extends Command<void> {
); );
} }
void addEnableExperimentation({ bool verbose }) {
argParser.addMultiOption(
FlutterOptions.kEnableExperiment,
help:
'The name of an experimental Dart feature to enable. For more info '
'see: https://github.com/dart-lang/sdk/blob/master/docs/process/'
'experimental-flags.md',
hide: !verbose,
);
}
set defaultBuildMode(BuildMode value) { set defaultBuildMode(BuildMode value) {
_defaultBuildMode = value; _defaultBuildMode = value;
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
...@@ -72,6 +73,7 @@ void main() { ...@@ -72,6 +73,7 @@ void main() {
processManager: processManager, processManager: processManager,
logger: logger, logger: logger,
terminal: terminal, terminal: terminal,
experiments: <String>[],
); );
int errorCount = 0; int errorCount = 0;
...@@ -96,6 +98,7 @@ void main() { ...@@ -96,6 +98,7 @@ void main() {
processManager: processManager, processManager: processManager,
logger: logger, logger: logger,
terminal: terminal, terminal: terminal,
experiments: <String>[],
); );
int errorCount = 0; int errorCount = 0;
...@@ -119,6 +122,7 @@ void main() { ...@@ -119,6 +122,7 @@ void main() {
processManager: processManager, processManager: processManager,
logger: logger, logger: logger,
terminal: terminal, terminal: terminal,
experiments: <String>[],
); );
int errorCount = 0; int errorCount = 0;
...@@ -130,4 +134,39 @@ void main() { ...@@ -130,4 +134,39 @@ void main() {
await onDone; await onDone;
expect(errorCount, 0); expect(errorCount, 0);
}); });
testWithoutContext('Can forward null-safety experiments to the AnalysisServer', () async {
final Completer<void> completer = Completer<void>();
final StreamController<List<int>> stdin = StreamController<List<int>>();
const String fakeSdkPath = 'dart-sdk';
final FakeCommand fakeCommand = FakeCommand(
command: const <String>[
'dart-sdk/bin/dart',
'dart-sdk/bin/snapshots/analysis_server.dart.snapshot',
'--enable-experiment',
'non-nullable',
'--disable-server-feature-completion',
'--disable-server-feature-search',
'--sdk',
'dart-sdk',
],
completer: completer,
stdin: IOSink(stdin.sink),
);
server = AnalysisServer(fakeSdkPath, <String>[''],
fileSystem: MemoryFileSystem.test(),
platform: FakePlatform(),
processManager: FakeProcessManager.list(<FakeCommand>[
fakeCommand,
]),
logger: BufferLogger.test(),
terminal: Terminal.test(),
experiments: <String>[
'non-nullable'
],
);
await server.start();
});
} }
...@@ -11,54 +11,44 @@ import '../../src/common.dart'; ...@@ -11,54 +11,44 @@ import '../../src/common.dart';
const String _kFlutterRoot = '/data/flutter'; const String _kFlutterRoot = '/data/flutter';
void main() { void main() {
FileSystem fs; testWithoutContext('analyze inRepo', () {
Directory tempDir; final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.directory(_kFlutterRoot).createSync(recursive: true);
setUp(() { final Directory tempDir = fileSystem.systemTempDirectory
fs = MemoryFileSystem(); .createTempSync('flutter_analysis_test.');
fs.directory(_kFlutterRoot).createSync(recursive: true);
Cache.flutterRoot = _kFlutterRoot; Cache.flutterRoot = _kFlutterRoot;
tempDir = fs.systemTempDirectory.createTempSync('flutter_analysis_test.');
});
tearDown(() { // Absolute paths
tryToDelete(tempDir); expect(inRepo(<String>[tempDir.path], fileSystem), isFalse);
expect(inRepo(<String>[fileSystem.path.join(tempDir.path, 'foo')], fileSystem), isFalse);
expect(inRepo(<String>[Cache.flutterRoot], fileSystem), isTrue);
expect(inRepo(<String>[fileSystem.path.join(Cache.flutterRoot, 'foo')], fileSystem), isTrue);
// Relative paths
fileSystem.currentDirectory = Cache.flutterRoot;
expect(inRepo(<String>['.'], fileSystem), isTrue);
expect(inRepo(<String>['foo'], fileSystem), isTrue);
fileSystem.currentDirectory = tempDir.path;
expect(inRepo(<String>['.'], fileSystem), isFalse);
expect(inRepo(<String>['foo'], fileSystem), isFalse);
// Ensure no exceptions
inRepo(null, fileSystem);
inRepo(<String>[], fileSystem);
}); });
}
group('analyze', () { bool inRepo(List<String> fileList, FileSystem fileSystem) {
testWithoutContext('inRepo', () { if (fileList == null || fileList.isEmpty) {
bool inRepo(List<String> fileList) { fileList = <String>[fileSystem.path.current];
if (fileList == null || fileList.isEmpty) { }
fileList = <String>[fs.path.current]; final String root = fileSystem.path.normalize(fileSystem.path.absolute(Cache.flutterRoot));
} final String prefix = root + fileSystem.path.separator;
final String root = fs.path.normalize(fs.path.absolute(Cache.flutterRoot)); for (String file in fileList) {
final String prefix = root + fs.path.separator; file = fileSystem.path.normalize(fileSystem.path.absolute(file));
for (String file in fileList) { if (file == root || file.startsWith(prefix)) {
file = fs.path.normalize(fs.path.absolute(file)); return true;
if (file == root || file.startsWith(prefix)) { }
return true; }
} return false;
}
return false;
}
// Absolute paths
expect(inRepo(<String>[tempDir.path]), isFalse);
expect(inRepo(<String>[fs.path.join(tempDir.path, 'foo')]), isFalse);
expect(inRepo(<String>[Cache.flutterRoot]), isTrue);
expect(inRepo(<String>[fs.path.join(Cache.flutterRoot, 'foo')]), isTrue);
// Relative paths
fs.currentDirectory = Cache.flutterRoot;
expect(inRepo(<String>['.']), isTrue);
expect(inRepo(<String>['foo']), isTrue);
fs.currentDirectory = tempDir.path;
expect(inRepo(<String>['.']), isFalse);
expect(inRepo(<String>['foo']), isFalse);
// Ensure no exceptions
inRepo(null);
inRepo(<String>[]);
});
});
} }
...@@ -304,6 +304,32 @@ StringBuffer bar = StringBuffer('baz'); ...@@ -304,6 +304,32 @@ StringBuffer bar = StringBuffer('baz');
} }
}); });
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,
),
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 { testUsingContext('analyze once returns no issues for todo comments', () async {
const String contents = ''' const String contents = '''
// TODO(foobar): // TODO(foobar):
......
...@@ -28,6 +28,7 @@ class FakeCommand { ...@@ -28,6 +28,7 @@ class FakeCommand {
this.stdout = '', this.stdout = '',
this.stderr = '', this.stderr = '',
this.completer, this.completer,
this.stdin,
}) : assert(command != null), }) : assert(command != null),
assert(duration != null), assert(duration != null),
assert(exitCode != null); assert(exitCode != null);
...@@ -82,6 +83,10 @@ class FakeCommand { ...@@ -82,6 +83,10 @@ class FakeCommand {
/// resolves. /// resolves.
final Completer<void> completer; final Completer<void> completer;
/// An optional stdin sink that will be exposed through the resulting
/// [FakeProcess].
final IOSink stdin;
void _matches(List<String> command, String workingDirectory, Map<String, String> environment) { void _matches(List<String> command, String workingDirectory, Map<String, String> environment) {
expect(command, equals(this.command)); expect(command, equals(this.command));
if (this.workingDirectory != null) { if (this.workingDirectory != null) {
...@@ -198,7 +203,7 @@ abstract class FakeProcessManager implements ProcessManager { ...@@ -198,7 +203,7 @@ abstract class FakeProcessManager implements ProcessManager {
fakeCommand.onRun, fakeCommand.onRun,
_pid, _pid,
fakeCommand.stderr, fakeCommand.stderr,
null, // stdin fakeCommand.stdin,
fakeCommand.stdout, fakeCommand.stdout,
fakeCommand.completer, fakeCommand.completer,
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment