[tool] xcresult issue discarder (#95273)
This commit is contained in:
parent
82bf318968
commit
e4dadf788b
@ -35,7 +35,11 @@ class XCResultGenerator {
|
|||||||
///
|
///
|
||||||
/// Calls `xcrun xcresulttool get --path <resultPath> --format json`,
|
/// Calls `xcrun xcresulttool get --path <resultPath> --format json`,
|
||||||
/// then stores the useful information the json into an [XCResult] object.
|
/// then stores the useful information the json into an [XCResult] object.
|
||||||
Future<XCResult> generate() async {
|
///
|
||||||
|
/// A`issueDiscarders` can be passed to discard any issues that matches the description of any [XCResultIssueDiscarder] in the list.
|
||||||
|
Future<XCResult> generate(
|
||||||
|
{List<XCResultIssueDiscarder> issueDiscarders =
|
||||||
|
const <XCResultIssueDiscarder>[]}) async {
|
||||||
final RunResult result = await processUtils.run(
|
final RunResult result = await processUtils.run(
|
||||||
<String>[
|
<String>[
|
||||||
...xcode.xcrunCommand(),
|
...xcode.xcrunCommand(),
|
||||||
@ -62,7 +66,7 @@ class XCResultGenerator {
|
|||||||
return XCResult.failed(
|
return XCResult.failed(
|
||||||
errorMessage: 'xcresult parser: Unrecognized top level json format.');
|
errorMessage: 'xcresult parser: Unrecognized top level json format.');
|
||||||
}
|
}
|
||||||
return XCResult(resultJson: resultJson);
|
return XCResult(resultJson: resultJson, issueDiscarders: issueDiscarders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +76,7 @@ class XCResultGenerator {
|
|||||||
/// The result contains useful information such as build errors and warnings.
|
/// The result contains useful information such as build errors and warnings.
|
||||||
class XCResult {
|
class XCResult {
|
||||||
/// Parse the `resultJson` and stores useful informations in the returned `XCResult`.
|
/// Parse the `resultJson` and stores useful informations in the returned `XCResult`.
|
||||||
factory XCResult({required Map<String, Object?> resultJson}) {
|
factory XCResult({required Map<String, Object?> resultJson, List<XCResultIssueDiscarder> issueDiscarders = const <XCResultIssueDiscarder>[]}) {
|
||||||
final List<XCResultIssue> issues = <XCResultIssue>[];
|
final List<XCResultIssue> issues = <XCResultIssue>[];
|
||||||
final Object? actionsMap = resultJson['actions'];
|
final Object? actionsMap = resultJson['actions'];
|
||||||
if (actionsMap == null || actionsMap is! Map<String, Object?>) {
|
if (actionsMap == null || actionsMap is! Map<String, Object?>) {
|
||||||
@ -103,32 +107,13 @@ class XCResult {
|
|||||||
return XCResult.failed(
|
return XCResult.failed(
|
||||||
errorMessage: 'xcresult parser: Failed to parse the issues map.');
|
errorMessage: 'xcresult parser: Failed to parse the issues map.');
|
||||||
}
|
}
|
||||||
List<XCResultIssue> _parseIssuesFromIssueSummariesJson({
|
|
||||||
required XCResultIssueType type,
|
|
||||||
required Map<String, Object?> issueSummariesJson,
|
|
||||||
}) {
|
|
||||||
final List<XCResultIssue> issues = <XCResultIssue>[];
|
|
||||||
final Object? errorsList = issueSummariesJson['_values'];
|
|
||||||
if (errorsList is List<Object?>) {
|
|
||||||
for (final Object? issueJson in errorsList) {
|
|
||||||
if (issueJson == null || issueJson is! Map<String, Object?>) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final XCResultIssue resultIssue = XCResultIssue(
|
|
||||||
type: type,
|
|
||||||
issueJson: issueJson,
|
|
||||||
);
|
|
||||||
issues.add(resultIssue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return issues;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Object? errorSummaries = issuesMap['errorSummaries'];
|
final Object? errorSummaries = issuesMap['errorSummaries'];
|
||||||
if (errorSummaries is Map<String, Object?>) {
|
if (errorSummaries is Map<String, Object?>) {
|
||||||
issues.addAll(_parseIssuesFromIssueSummariesJson(
|
issues.addAll(_parseIssuesFromIssueSummariesJson(
|
||||||
type: XCResultIssueType.error,
|
type: XCResultIssueType.error,
|
||||||
issueSummariesJson: errorSummaries,
|
issueSummariesJson: errorSummaries,
|
||||||
|
issueDiscarder: issueDiscarders,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +122,7 @@ class XCResult {
|
|||||||
issues.addAll(_parseIssuesFromIssueSummariesJson(
|
issues.addAll(_parseIssuesFromIssueSummariesJson(
|
||||||
type: XCResultIssueType.warning,
|
type: XCResultIssueType.warning,
|
||||||
issueSummariesJson: warningSummaries,
|
issueSummariesJson: warningSummaries,
|
||||||
|
issueDiscarder: issueDiscarders,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return XCResult._(issues: issues);
|
return XCResult._(issues: issues);
|
||||||
@ -210,7 +196,8 @@ class XCResultIssue {
|
|||||||
if (urlValue is String) {
|
if (urlValue is String) {
|
||||||
location = _convertUrlToLocationString(urlValue);
|
location = _convertUrlToLocationString(urlValue);
|
||||||
if (location == null) {
|
if (location == null) {
|
||||||
warnings.add('(XCResult) The `url` exists but it was failed to be parsed. url: $urlValue');
|
warnings.add(
|
||||||
|
'(XCResult) The `url` exists but it was failed to be parsed. url: $urlValue');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,6 +257,39 @@ enum XCResultIssueType {
|
|||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Discards the [XCResultIssue] that matches any of the matchers.
|
||||||
|
class XCResultIssueDiscarder {
|
||||||
|
XCResultIssueDiscarder(
|
||||||
|
{this.typeMatcher,
|
||||||
|
this.subTypeMatcher,
|
||||||
|
this.messageMatcher,
|
||||||
|
this.locationMatcher})
|
||||||
|
: assert(typeMatcher != null ||
|
||||||
|
subTypeMatcher != null ||
|
||||||
|
messageMatcher != null ||
|
||||||
|
locationMatcher != null);
|
||||||
|
|
||||||
|
/// The type of the discarder.
|
||||||
|
///
|
||||||
|
/// A [XCResultIssue] should be discarded if its `type` equals to this.
|
||||||
|
final XCResultIssueType? typeMatcher;
|
||||||
|
|
||||||
|
/// The subType of the discarder.
|
||||||
|
///
|
||||||
|
/// A [XCResultIssue] should be discarded if its `subType` matches the RegExp.
|
||||||
|
final RegExp? subTypeMatcher;
|
||||||
|
|
||||||
|
/// The message of the discarder.
|
||||||
|
///
|
||||||
|
/// A [XCResultIssue] should be discarded if its `message` matches the RegExp.
|
||||||
|
final RegExp? messageMatcher;
|
||||||
|
|
||||||
|
/// The location of the discarder.
|
||||||
|
///
|
||||||
|
/// A [XCResultIssue] should be discarded if its `location` matches the RegExp.
|
||||||
|
final RegExp? locationMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
// A typical location url string looks like file:///foo.swift#CharacterRangeLen=0&EndingColumnNumber=82&EndingLineNumber=7&StartingColumnNumber=82&StartingLineNumber=7.
|
// A typical location url string looks like file:///foo.swift#CharacterRangeLen=0&EndingColumnNumber=82&EndingLineNumber=7&StartingColumnNumber=82&StartingLineNumber=7.
|
||||||
//
|
//
|
||||||
// This function converts it to something like: /foo.swift:<StartingLineNumber>:<StartingColumnNumber>.
|
// This function converts it to something like: /foo.swift:<StartingLineNumber>:<StartingColumnNumber>.
|
||||||
@ -295,3 +315,60 @@ String? _convertUrlToLocationString(String url) {
|
|||||||
}
|
}
|
||||||
return '${fileLocation.path}$startingLineNumber$startingColumnNumber';
|
return '${fileLocation.path}$startingLineNumber$startingColumnNumber';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if an `issue` should be discarded based on the `discarder`.
|
||||||
|
bool _shouldDiscardIssue(
|
||||||
|
{required XCResultIssue issue, required XCResultIssueDiscarder discarder}) {
|
||||||
|
if (issue.type == discarder.typeMatcher) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (issue.subType != null &&
|
||||||
|
discarder.subTypeMatcher != null &&
|
||||||
|
discarder.subTypeMatcher!.hasMatch(issue.subType!)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (issue.message != null &&
|
||||||
|
discarder.messageMatcher != null &&
|
||||||
|
discarder.messageMatcher!.hasMatch(issue.message!)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (issue.location != null &&
|
||||||
|
discarder.locationMatcher != null &&
|
||||||
|
discarder.locationMatcher!.hasMatch(issue.location!)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XCResultIssue> _parseIssuesFromIssueSummariesJson({
|
||||||
|
required XCResultIssueType type,
|
||||||
|
required Map<String, Object?> issueSummariesJson,
|
||||||
|
required List<XCResultIssueDiscarder> issueDiscarder,
|
||||||
|
}) {
|
||||||
|
final List<XCResultIssue> issues = <XCResultIssue>[];
|
||||||
|
final Object? errorsList = issueSummariesJson['_values'];
|
||||||
|
if (errorsList is List<Object?>) {
|
||||||
|
for (final Object? issueJson in errorsList) {
|
||||||
|
if (issueJson == null || issueJson is! Map<String, Object?>) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final XCResultIssue resultIssue = XCResultIssue(
|
||||||
|
type: type,
|
||||||
|
issueJson: issueJson,
|
||||||
|
);
|
||||||
|
bool discard = false;
|
||||||
|
for (final XCResultIssueDiscarder discarder in issueDiscarder) {
|
||||||
|
if (_shouldDiscardIssue(issue: resultIssue, discarder: discarder)) {
|
||||||
|
discard = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (discard) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
issues.add(resultIssue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
@ -128,6 +128,72 @@ void main() {
|
|||||||
expect(result.parsingErrorMessage, isNull);
|
expect(result.parsingErrorMessage, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWithoutContext(
|
||||||
|
'correctly parse sample result json and discard all warnings', () async {
|
||||||
|
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonWithIssues);
|
||||||
|
final XCResultIssueDiscarder discarder = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
|
||||||
|
final XCResult result = await generator.generate(issueDiscarders: <XCResultIssueDiscarder>[discarder]);
|
||||||
|
expect(result.issues.length, 1);
|
||||||
|
expect(result.issues.first.type, XCResultIssueType.error);
|
||||||
|
expect(result.issues.first.subType, 'Semantic Issue');
|
||||||
|
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
|
||||||
|
expect(result.issues.first.location, '/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56');
|
||||||
|
expect(result.parseSuccess, isTrue);
|
||||||
|
expect(result.parsingErrorMessage, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext(
|
||||||
|
'correctly parse sample result json and discard base on subType', () async {
|
||||||
|
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonWithIssues);
|
||||||
|
final XCResultIssueDiscarder discarder = XCResultIssueDiscarder(subTypeMatcher: RegExp(r'^Warning$'));
|
||||||
|
final XCResult result = await generator.generate(issueDiscarders: <XCResultIssueDiscarder>[discarder]);
|
||||||
|
expect(result.issues.length, 1);
|
||||||
|
expect(result.issues.first.type, XCResultIssueType.error);
|
||||||
|
expect(result.issues.first.subType, 'Semantic Issue');
|
||||||
|
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
|
||||||
|
expect(result.issues.first.location, '/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56');
|
||||||
|
expect(result.parseSuccess, isTrue);
|
||||||
|
expect(result.parsingErrorMessage, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext(
|
||||||
|
'correctly parse sample result json and discard base on message', () async {
|
||||||
|
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonWithIssues);
|
||||||
|
final XCResultIssueDiscarder discarder = XCResultIssueDiscarder(messageMatcher: RegExp(r"^The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.$"));
|
||||||
|
final XCResult result = await generator.generate(issueDiscarders: <XCResultIssueDiscarder>[discarder]);
|
||||||
|
expect(result.issues.length, 1);
|
||||||
|
expect(result.issues.first.type, XCResultIssueType.error);
|
||||||
|
expect(result.issues.first.subType, 'Semantic Issue');
|
||||||
|
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
|
||||||
|
expect(result.issues.first.location, '/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56');
|
||||||
|
expect(result.parseSuccess, isTrue);
|
||||||
|
expect(result.parsingErrorMessage, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext(
|
||||||
|
'correctly parse sample result json and discard base on location', () async {
|
||||||
|
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonWithIssues);
|
||||||
|
final XCResultIssueDiscarder discarder = XCResultIssueDiscarder(locationMatcher: RegExp(r'/Users/m/Projects/test_create/ios/Runner/AppDelegate.m'));
|
||||||
|
final XCResult result = await generator.generate(issueDiscarders: <XCResultIssueDiscarder>[discarder]);
|
||||||
|
expect(result.issues.length, 1);
|
||||||
|
expect(result.issues.first.type, XCResultIssueType.warning);
|
||||||
|
expect(result.issues.first.subType, 'Warning');
|
||||||
|
expect(result.issues.first.message,
|
||||||
|
"The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.");
|
||||||
|
expect(result.parseSuccess, isTrue);
|
||||||
|
expect(result.parsingErrorMessage, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext(
|
||||||
|
'correctly parse sample result json with multiple discarders.', () async {
|
||||||
|
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonWithIssues);
|
||||||
|
final XCResultIssueDiscarder discardWarnings = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
|
||||||
|
final XCResultIssueDiscarder discardSemanticIssues = XCResultIssueDiscarder(subTypeMatcher: RegExp(r'^Semantic Issue$'));
|
||||||
|
final XCResult result = await generator.generate(issueDiscarders: <XCResultIssueDiscarder>[discardWarnings, discardSemanticIssues]);
|
||||||
|
expect(result.issues, isEmpty);
|
||||||
|
expect(result.parseSuccess, isTrue);
|
||||||
|
expect(result.parsingErrorMessage, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
testWithoutContext('correctly parse sample result json when no issues.',
|
testWithoutContext('correctly parse sample result json when no issues.',
|
||||||
() async {
|
() async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user