Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flutter_tools] Separate style and data from AnalysisError #60591

Merged
merged 8 commits into from Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
153 changes: 104 additions & 49 deletions packages/flutter_tools/lib/src/dart/analysis.dart
Expand Up @@ -19,7 +19,9 @@ import '../convert.dart';

/// An interface to the Dart analysis server.
class AnalysisServer {
AnalysisServer(this.sdkPath, this.directories, {
AnalysisServer(
this.sdkPath,
this.directories, {
@required FileSystem fileSystem,
@required ProcessManager processManager,
@required Logger logger,
Expand Down Expand Up @@ -78,12 +80,14 @@ class AnalysisServer {
// This callback hookup can't throw.
unawaited(_process.exitCode.whenComplete(() => _process = null));

final Stream<String> errorStream =
_process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter());
final Stream<String> errorStream = _process.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter());
errorStream.listen(_logger.printError);

final Stream<String> inStream =
_process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter());
final Stream<String> inStream = _process.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter());
inStream.listen(_handleServerResponse);

_sendCommand('server.setSubscriptions', <String, dynamic>{
Expand All @@ -95,7 +99,9 @@ class AnalysisServer {
}

bool get didServerErrorOccur => _didServerErrorOccur;

Stream<bool> get onAnalyzing => _analyzingController.stream;

Stream<FileAnalysisErrors> get onErrors => _errorsController.stream;

Future<int> get onExit => _process.exitCode;
Expand Down Expand Up @@ -165,7 +171,7 @@ class AnalysisServer {
final List<AnalysisError> errors = errorsList
.map<Map<String, dynamic>>(castStringKeyedMap)
.map<AnalysisError>((Map<String, dynamic> json) {
return AnalysisError(json,
return AnalysisError(WrittenError.fromJson(json),
fileSystem: _fileSystem,
platform: _platform,
terminal: _terminal,
Expand All @@ -191,94 +197,143 @@ enum _AnalysisSeverity {
none,
}

/// [AnalysisError] with command line style.
class AnalysisError implements Comparable<AnalysisError> {
AnalysisError(this.json, {
AnalysisError(
this.writtenError, {
@required Platform platform,
@required Terminal terminal,
@required FileSystem fileSystem,
}) : _platform = platform,
_terminal = terminal,
_fileSystem = fileSystem;

final WrittenError writtenError;
final Platform _platform;
final Terminal _terminal;
final FileSystem _fileSystem;

static final Map<String, _AnalysisSeverity> _severityMap = <String, _AnalysisSeverity>{
'INFO': _AnalysisSeverity.info,
'WARNING': _AnalysisSeverity.warning,
'ERROR': _AnalysisSeverity.error,
};

String get _separator => _platform.isWindows ? '-' : '•';
String get _separator => _platform.isWindows ? '-' : '•';

// "severity":"INFO","type":"TODO","location":{
// "file":"/Users/.../lib/test.dart","offset":362,"length":72,"startLine":15,"startColumn":4
// },"message":"...","hasFix":false}
Map<String, dynamic> json;

String get severity => json['severity'] as String;
String get colorSeverity {
switch(_severityLevel) {
switch (writtenError.severityLevel) {
case _AnalysisSeverity.error:
return _terminal.color(severity, TerminalColor.red);
return _terminal.color(writtenError.severity, TerminalColor.red);
case _AnalysisSeverity.warning:
return _terminal.color(severity, TerminalColor.yellow);
return _terminal.color(writtenError.severity, TerminalColor.yellow);
case _AnalysisSeverity.info:
case _AnalysisSeverity.none:
return severity;
return writtenError.severity;
}
return null;
}
_AnalysisSeverity get _severityLevel => _severityMap[severity] ?? _AnalysisSeverity.none;
String get type => json['type'] as String;
String get message => json['message'] as String;
String get code => json['code'] as String;

String get file => json['location']['file'] as String;
int get startLine => json['location']['startLine'] as int;
int get startColumn => json['location']['startColumn'] as int;
int get offset => json['location']['offset'] as int;

String get messageSentenceFragment {
if (message.endsWith('.')) {
return message.substring(0, message.length - 1);
} else {
return message;
}
}
String get type => writtenError.type;
String get code => writtenError.code;

@override
int compareTo(AnalysisError other) {
// Sort in order of file path, error location, severity, and message.
if (file != other.file) {
return file.compareTo(other.file);
if (writtenError.file != other.writtenError.file) {
return writtenError.file.compareTo(other.writtenError.file);
}

if (offset != other.offset) {
return offset - other.offset;
if (writtenError.offset != other.writtenError.offset) {
return writtenError.offset - other.writtenError.offset;
}

final int diff = other._severityLevel.index - _severityLevel.index;
final int diff = other.writtenError.severityLevel.index -
writtenError.severityLevel.index;
if (diff != 0) {
return diff;
}

return message.compareTo(other.message);
return writtenError.message.compareTo(other.writtenError.message);
}

@override
String toString() {
// Can't use "padLeft" because of ANSI color sequences in the colorized
// severity.
final String padding = ' ' * math.max(0, 7 - severity.length);
final String padding = ' ' * math.max(0, 7 - writtenError.severity.length);
return '$padding${colorSeverity.toLowerCase()} $_separator '
'$messageSentenceFragment $_separator '
'${_fileSystem.path.relative(file)}:$startLine:$startColumn $_separator '
'${writtenError.messageSentenceFragment} $_separator '
'${_fileSystem.path.relative(writtenError.file)}:${writtenError.startLine}:${writtenError.startColumn} $_separator '
'$code';
}

String toLegacyString() {
return writtenError.toString();
}
}

/// [AnalysisError] in plain text content.
class WrittenError {
WrittenError._({
@required this.severity,
@required this.type,
@required this.message,
@required this.code,
@required this.file,
@required this.startLine,
@required this.startColumn,
@required this.offset,
});

/// {
/// "severity":"INFO",
/// "type":"TODO",
/// "location":{
/// "file":"/Users/.../lib/test.dart",
/// "offset":362,
/// "length":72,
/// "startLine":15,
/// "startColumn":4
/// },
/// "message":"...",
/// "hasFix":false
/// }
static WrittenError fromJson(Map<String, dynamic> json) {
return WrittenError._(
severity: json['severity'] as String,
type: json['type'] as String,
message: json['message'] as String,
code: json['code'] as String,
file: json['location']['file'] as String,
startLine: json['location']['startLine'] as int,
startColumn: json['location']['startColumn'] as int,
offset: json['location']['offset'] as int,
);
}

final String severity;
final String type;
final String message;
final String code;

final String file;
final int startLine;
final int startColumn;
final int offset;

static final Map<String, _AnalysisSeverity> _severityMap = <String, _AnalysisSeverity>{
'INFO': _AnalysisSeverity.info,
'WARNING': _AnalysisSeverity.warning,
'ERROR': _AnalysisSeverity.error,
};

_AnalysisSeverity get severityLevel =>
_severityMap[severity] ?? _AnalysisSeverity.none;

String get messageSentenceFragment {
if (message.endsWith('.')) {
return message.substring(0, message.length - 1);
}
return message;
}

@override
String toString() {
return '[${severity.toLowerCase()}] $messageSentenceFragment ($file:$startLine:$startColumn)';
}
}
Expand Down
Expand Up @@ -6,6 +6,7 @@ import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/analyze_base.dart';
import 'package:flutter_tools/src/dart/analysis.dart';

import '../../src/common.dart';

Expand Down Expand Up @@ -43,6 +44,24 @@ void main() {
inRepo(null, fileSystem);
inRepo(<String>[], fileSystem);
});

testWithoutContext('AnalysisError from json write correct', () {
final Map<String, dynamic> json = <String, dynamic>{
'severity': 'INFO',
'type': 'TODO',
'location': <String, dynamic>{
'file': '/Users/.../lib/test.dart',
'offset': 362,
'length': 72,
'startLine': 15,
'startColumn': 4
},
'message': 'Prefer final for variable declarations if they are not reassigned.',
'hasFix': false
};
expect(WrittenError.fromJson(json).toString(),
'[info] Prefer final for variable declarations if they are not reassigned (/Users/.../lib/test.dart:15:4)');
});
}

bool inRepo(List<String> fileList, FileSystem fileSystem) {
Expand Down