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 {
static String colorCode(TerminalColor color) => _colorMap[color];
@override
bool get supportsColor => _platform.stdoutSupportsAnsi ?? false;
bool get supportsColor => _platform?.stdoutSupportsAnsi ?? false;
// Assume unicode emojis are supported when not on Windows.
// If we are on Windows, unicode emojis are supported in Windows Terminal,
......
......@@ -30,6 +30,7 @@ class AnalyzeCommand extends FlutterCommand {
_logger = logger,
_terminal = terminal,
_platform = platform {
addEnableExperimentation(verbose: verboseHelp);
argParser.addFlag('flutter-repo',
negatable: false,
help: 'Include all the examples and tests from the Flutter repository.',
......@@ -118,6 +119,7 @@ class AnalyzeCommand extends FlutterCommand {
platform: _platform,
processManager: _processManager,
terminal: _terminal,
experiments: stringsArg('enable-experiment'),
).analyze();
} else {
await AnalyzeOnce(
......@@ -132,6 +134,7 @@ class AnalyzeCommand extends FlutterCommand {
platform: _platform,
processManager: _processManager,
terminal: _terminal,
experiments: stringsArg('enable-experiment'),
).analyze();
}
return FlutterCommandResult.success();
......
......@@ -28,6 +28,7 @@ abstract class AnalyzeBase {
@required this.platform,
@required this.processManager,
@required this.terminal,
@required this.experiments,
});
/// The parsed argument results for execution.
......@@ -46,6 +47,8 @@ abstract class AnalyzeBase {
final Platform platform;
@protected
final AnsiTerminal terminal;
@protected
final List<String> experiments;
/// Called by [AnalyzeCommand] to start the analysis process.
Future<void> analyze();
......
......@@ -27,6 +27,7 @@ class AnalyzeContinuously extends AnalyzeBase {
@required AnsiTerminal terminal,
@required Platform platform,
@required ProcessManager processManager,
@required List<String> experiments,
}) : super(
argResults,
repoPackages: repoPackages,
......@@ -36,6 +37,7 @@ class AnalyzeContinuously extends AnalyzeBase {
platform: platform,
terminal: terminal,
processManager: processManager,
experiments: experiments,
);
String analysisTarget;
......@@ -74,6 +76,7 @@ class AnalyzeContinuously extends AnalyzeBase {
platform: platform,
processManager: processManager,
terminal: terminal,
experiments: experiments,
);
server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing));
server.onErrors.listen(_handleAnalysisErrors);
......
......@@ -31,6 +31,7 @@ class AnalyzeOnce extends AnalyzeBase {
@required Platform platform,
@required ProcessManager processManager,
@required AnsiTerminal terminal,
@required List<String> experiments,
this.workingDirectory,
}) : super(
argResults,
......@@ -41,6 +42,7 @@ class AnalyzeOnce extends AnalyzeBase {
platform: platform,
processManager: processManager,
terminal: terminal,
experiments: experiments,
);
/// The working directory for testing analysis using dartanalyzer.
......@@ -98,6 +100,7 @@ class AnalyzeOnce extends AnalyzeBase {
logger: logger,
processManager: processManager,
terminal: terminal,
experiments: experiments,
);
StreamSubscription<bool> subscription;
......
......@@ -24,12 +24,14 @@ class AnalysisServer {
@required ProcessManager processManager,
@required Logger logger,
@required Platform platform,
@required AnsiTerminal terminal,
@required Terminal terminal,
@required List<String> experiments,
}) : _fileSystem = fileSystem,
_processManager = processManager,
_logger = logger,
_platform = platform,
_terminal = terminal;
_terminal = terminal,
_experiments = experiments;
final String sdkPath;
final List<String> directories;
......@@ -37,7 +39,8 @@ class AnalysisServer {
final ProcessManager _processManager;
final Logger _logger;
final Platform _platform;
final AnsiTerminal _terminal;
final Terminal _terminal;
final List<String> _experiments;
Process _process;
final StreamController<bool> _analyzingController =
......@@ -49,11 +52,20 @@ class AnalysisServer {
int _id = 0;
Future<void> start() async {
final String snapshot =
_fileSystem.path.join(sdkPath, 'bin/snapshots/analysis_server.dart.snapshot');
final String snapshot = _fileSystem.path.join(
sdkPath,
'bin',
'snapshots',
'analysis_server.dart.snapshot',
);
final List<String> command = <String>[
_fileSystem.path.join(sdkPath, 'bin', 'dart'),
snapshot,
for (String experiment in _experiments)
...<String>[
'--enable-experiment',
experiment,
],
'--disable-server-feature-completion',
'--disable-server-feature-search',
'--sdk',
......@@ -181,14 +193,14 @@ enum _AnalysisSeverity {
class AnalysisError implements Comparable<AnalysisError> {
AnalysisError(this.json, {
@required Platform platform,
@required AnsiTerminal terminal,
@required Terminal terminal,
@required FileSystem fileSystem,
}) : _platform = platform,
_terminal = terminal,
_fileSystem = fileSystem;
final Platform _platform;
final AnsiTerminal _terminal;
final Terminal _terminal;
final FileSystem _fileSystem;
static final Map<String, _AnalysisSeverity> _severityMap = <String, _AnalysisSeverity>{
......
......@@ -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) {
_defaultBuildMode = value;
}
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:file/memory.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';
......@@ -72,6 +73,7 @@ void main() {
processManager: processManager,
logger: logger,
terminal: terminal,
experiments: <String>[],
);
int errorCount = 0;
......@@ -96,6 +98,7 @@ void main() {
processManager: processManager,
logger: logger,
terminal: terminal,
experiments: <String>[],
);
int errorCount = 0;
......@@ -119,6 +122,7 @@ void main() {
processManager: processManager,
logger: logger,
terminal: terminal,
experiments: <String>[],
);
int errorCount = 0;
......@@ -130,4 +134,39 @@ void main() {
await onDone;
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';
const String _kFlutterRoot = '/data/flutter';
void main() {
FileSystem fs;
Directory tempDir;
setUp(() {
fs = MemoryFileSystem();
fs.directory(_kFlutterRoot).createSync(recursive: true);
testWithoutContext('analyze inRepo', () {
final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.directory(_kFlutterRoot).createSync(recursive: true);
final Directory tempDir = fileSystem.systemTempDirectory
.createTempSync('flutter_analysis_test.');
Cache.flutterRoot = _kFlutterRoot;
tempDir = fs.systemTempDirectory.createTempSync('flutter_analysis_test.');
});
tearDown(() {
tryToDelete(tempDir);
// Absolute paths
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', () {
testWithoutContext('inRepo', () {
bool inRepo(List<String> fileList) {
bool inRepo(List<String> fileList, FileSystem fileSystem) {
if (fileList == null || fileList.isEmpty) {
fileList = <String>[fs.path.current];
fileList = <String>[fileSystem.path.current];
}
final String root = fs.path.normalize(fs.path.absolute(Cache.flutterRoot));
final String prefix = root + fs.path.separator;
final String root = fileSystem.path.normalize(fileSystem.path.absolute(Cache.flutterRoot));
final String prefix = root + fileSystem.path.separator;
for (String file in fileList) {
file = fs.path.normalize(fs.path.absolute(file));
file = fileSystem.path.normalize(fileSystem.path.absolute(file));
if (file == root || file.startsWith(prefix)) {
return true;
}
}
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');
}
});
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 {
const String contents = '''
// TODO(foobar):
......
......@@ -28,6 +28,7 @@ class FakeCommand {
this.stdout = '',
this.stderr = '',
this.completer,
this.stdin,
}) : assert(command != null),
assert(duration != null),
assert(exitCode != null);
......@@ -82,6 +83,10 @@ class FakeCommand {
/// resolves.
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) {
expect(command, equals(this.command));
if (this.workingDirectory != null) {
......@@ -198,7 +203,7 @@ abstract class FakeProcessManager implements ProcessManager {
fakeCommand.onRun,
_pid,
fakeCommand.stderr,
null, // stdin
fakeCommand.stdin,
fakeCommand.stdout,
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