mirror of
https://github.com/dart-lang/sdk
synced 2024-10-04 16:54:55 +00:00
Migrate pkg/status_file to null safety.
Change-Id: Ic6627a64a3ee72a6f14aadf59ee87e21e64e3664 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209781 Auto-Submit: Bob Nystrom <rnystrom@google.com> Commit-Queue: Bob Nystrom <rnystrom@google.com> Reviewed-by: William Hesse <whesse@google.com>
This commit is contained in:
parent
c010c1d54f
commit
9e4cbbbe78
|
@ -11,7 +11,7 @@
|
|||
"constraint, update this by running tools/generate_package_config.dart."
|
||||
],
|
||||
"configVersion": 2,
|
||||
"generated": "2021-08-05T11:33:04.746536",
|
||||
"generated": "2021-08-10T10:51:47.272341",
|
||||
"generator": "tools/generate_package_config.dart",
|
||||
"packages": [
|
||||
{
|
||||
|
@ -659,7 +659,7 @@
|
|||
"name": "status_file",
|
||||
"rootUri": "../pkg/status_file",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.3"
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "stream_channel",
|
||||
|
|
|
@ -56,10 +56,11 @@ void main(List<String> arguments) {
|
|||
}
|
||||
|
||||
void lintStdIn({bool checkForDisjunctions = false}) {
|
||||
List<String> strings = <String>[];
|
||||
String readString;
|
||||
var strings = <String>[];
|
||||
try {
|
||||
while (null != (readString = stdin.readLineSync())) {
|
||||
while (true) {
|
||||
var readString = stdin.readLineSync();
|
||||
if (readString == null) break;
|
||||
strings.add(readString);
|
||||
}
|
||||
} on StdinException {
|
||||
|
@ -126,7 +127,7 @@ bool lintStatusFile(StatusFile statusFile,
|
|||
print("");
|
||||
return true;
|
||||
}
|
||||
if (statusFile.path != null && statusFile.path.isNotEmpty) {
|
||||
if (statusFile.path.isNotEmpty) {
|
||||
print("${statusFile.path}");
|
||||
}
|
||||
var errors = lintingErrors.toList();
|
||||
|
|
|
@ -33,8 +33,9 @@ import 'dart:io';
|
|||
import 'package:args/args.dart';
|
||||
import 'package:status_file/canonical_status_file.dart';
|
||||
import 'package:status_file/expectation.dart';
|
||||
import 'package:status_file/src/expression.dart';
|
||||
|
||||
StatusEntry filterExpectations(
|
||||
StatusEntry? filterExpectations(
|
||||
StatusEntry entry, List<Expectation> expectationsToKeep) {
|
||||
List<Expectation> remaining = entry.expectations
|
||||
.where(
|
||||
|
@ -45,14 +46,14 @@ StatusEntry filterExpectations(
|
|||
: StatusEntry(entry.path, entry.lineNumber, remaining, entry.comment);
|
||||
}
|
||||
|
||||
Map<String, Map<int, String>> issues;
|
||||
late Map<String, Map<int, String>> issues;
|
||||
|
||||
String getIssueState(String project, int issue) {
|
||||
Map projectIssues = issues[project];
|
||||
var projectIssues = issues[project];
|
||||
if (projectIssues == null) {
|
||||
throw "Cannot find project $project, not one of {${issues.keys.join(",")}}";
|
||||
}
|
||||
String state = projectIssues[issue] ?? "";
|
||||
var state = projectIssues[issue] ?? "";
|
||||
return "\t$state";
|
||||
}
|
||||
|
||||
|
@ -61,7 +62,7 @@ String getIssueState(String project, int issue) {
|
|||
// sorted by issue number then timestamp ascending.
|
||||
//
|
||||
// The first line is expected to contain the field names and is skipped.
|
||||
void parseIssueFile() async {
|
||||
Future<void> parseIssueFile() async {
|
||||
issues = {};
|
||||
String issuesLog = await File("issues.log").readAsString();
|
||||
List<String> lines = issuesLog.split("\n");
|
||||
|
@ -91,13 +92,13 @@ List<RegExp> sdkIssuePatterns = [
|
|||
];
|
||||
|
||||
String getIssueText(String comment, bool resolveState) {
|
||||
int issue;
|
||||
String prefix;
|
||||
String project;
|
||||
for (RegExp pattern in co19IssuePatterns) {
|
||||
Match match = pattern.firstMatch(comment);
|
||||
int? issue;
|
||||
late String prefix;
|
||||
late String project;
|
||||
for (var pattern in co19IssuePatterns) {
|
||||
var match = pattern.firstMatch(comment);
|
||||
if (match != null) {
|
||||
issue = int.tryParse(match[1]);
|
||||
issue = int.tryParse(match[1]!);
|
||||
if (issue != null) {
|
||||
prefix = "https://github.com/dart-lang/co19/issues/";
|
||||
project = "dart-lang/co19";
|
||||
|
@ -106,10 +107,10 @@ String getIssueText(String comment, bool resolveState) {
|
|||
}
|
||||
}
|
||||
if (issue == null) {
|
||||
for (RegExp pattern in sdkIssuePatterns) {
|
||||
Match match = pattern.firstMatch(comment);
|
||||
for (var pattern in sdkIssuePatterns) {
|
||||
var match = pattern.firstMatch(comment);
|
||||
if (match != null) {
|
||||
issue = int.tryParse(match[1]);
|
||||
issue = int.tryParse(match[1]!);
|
||||
if (issue != null) {
|
||||
prefix = "https://dartbug.com/";
|
||||
project = "dart-lang/sdk";
|
||||
|
@ -119,7 +120,7 @@ String getIssueText(String comment, bool resolveState) {
|
|||
}
|
||||
}
|
||||
if (issue != null) {
|
||||
String state = resolveState ? getIssueState(project, issue) : "";
|
||||
var state = resolveState ? getIssueState(project, issue) : "";
|
||||
return "$prefix$issue$state";
|
||||
} else {
|
||||
return "";
|
||||
|
@ -143,7 +144,7 @@ Future<StatusFile> removeNonEssentialEntries(
|
|||
entries.add(entry);
|
||||
hasStatusEntries = true;
|
||||
} else if (entry is StatusEntry) {
|
||||
StatusEntry newEntry = entry;
|
||||
StatusEntry? newEntry = entry;
|
||||
if (entry.comment == null) {
|
||||
newEntry = filterExpectations(entry, expectationsToKeep);
|
||||
} else if (removeComments) {
|
||||
|
@ -155,8 +156,9 @@ Future<StatusFile> removeNonEssentialEntries(
|
|||
String expectations = entry.expectations.toString();
|
||||
// Remove '[' and ']'.
|
||||
expectations = expectations.substring(1, expectations.length - 1);
|
||||
String conditionPrefix =
|
||||
section.condition != null ? "${section.condition}" : "";
|
||||
String conditionPrefix = section.condition != Expression.always
|
||||
? "${section.condition}"
|
||||
: "";
|
||||
String issueText = await getIssueText(comment, resolveIssueState);
|
||||
String statusLine = "$conditionPrefix\t$testName\t$expectations"
|
||||
"\t$comment\t$issueText";
|
||||
|
@ -171,16 +173,18 @@ Future<StatusFile> removeNonEssentialEntries(
|
|||
throw "Unknown entry type ${entry.runtimeType}";
|
||||
}
|
||||
}
|
||||
bool isDefaultSection = section.condition == null;
|
||||
|
||||
var isDefaultSection = section.condition == Expression.always;
|
||||
if (hasStatusEntries ||
|
||||
(isDefaultSection && section.sectionHeaderComments.isNotEmpty)) {
|
||||
StatusSection newSection =
|
||||
var newSection =
|
||||
StatusSection(section.condition, -1, section.sectionHeaderComments);
|
||||
newSection.entries.addAll(entries);
|
||||
sections.add(newSection);
|
||||
}
|
||||
}
|
||||
StatusFile newStatusFile = StatusFile(statusFile.path);
|
||||
|
||||
var newStatusFile = StatusFile(statusFile.path);
|
||||
newStatusFile.sections.addAll(sections);
|
||||
return newStatusFile;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ class StatusFile {
|
|||
|
||||
/// Checks if [currentLine] is a comment and returns the first regular
|
||||
/// expression match, or null otherwise.
|
||||
Match commentEntryMatch(int currentLine) {
|
||||
Match? commentEntryMatch(int currentLine) {
|
||||
if (currentLine < 1 || currentLine > lines.length) {
|
||||
return null;
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ class StatusFile {
|
|||
|
||||
/// Finds a section header on [currentLine] if the line is in range of
|
||||
/// [lines].
|
||||
Match sectionHeaderMatch(int currentLine) {
|
||||
Match? sectionHeaderMatch(int currentLine) {
|
||||
if (currentLine < 1 || currentLine > lines.length) {
|
||||
return null;
|
||||
}
|
||||
|
@ -178,14 +178,14 @@ class StatusFile {
|
|||
// The current section whose rules are being parsed. Initialized to an
|
||||
// implicit section that matches everything.
|
||||
StatusSection section =
|
||||
new StatusSection(null, -1, implicitSectionHeaderComments);
|
||||
new StatusSection(Expression.always, -1, implicitSectionHeaderComments);
|
||||
section.entries.addAll(entries);
|
||||
sections.add(section);
|
||||
|
||||
for (; _lineCount <= lines.length; _lineCount++) {
|
||||
var line = lines[_lineCount - 1];
|
||||
|
||||
fail(String message, [List<String> errors]) {
|
||||
fail(String message, [List<String>? errors]) {
|
||||
throw new SyntaxError(_shortPath, _lineCount, line, message, errors);
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ class StatusFile {
|
|||
var match = _sectionPattern.firstMatch(line);
|
||||
if (match != null) {
|
||||
try {
|
||||
var condition = Expression.parse(match[1].trim());
|
||||
var condition = Expression.parse(match[1]!.trim());
|
||||
section =
|
||||
new StatusSection(condition, _lineCount, sectionHeaderComments);
|
||||
sections.add(section);
|
||||
|
@ -214,10 +214,10 @@ class StatusFile {
|
|||
// If it is in a new entry we should add to the current section.
|
||||
match = _entryPattern.firstMatch(line);
|
||||
if (match != null) {
|
||||
var path = match[1].trim();
|
||||
var path = match[1]!.trim();
|
||||
var expectations = <Expectation>[];
|
||||
// split expectations
|
||||
match[2].split(",").forEach((name) {
|
||||
match[2]!.split(",").forEach((name) {
|
||||
try {
|
||||
expectations.add(Expectation.find(name.trim()));
|
||||
} on ArgumentError {
|
||||
|
@ -229,7 +229,7 @@ class StatusFile {
|
|||
.add(new StatusEntry(path, _lineCount, expectations, null));
|
||||
} else {
|
||||
section.entries.add(new StatusEntry(
|
||||
path, _lineCount, expectations, new Comment(match[3])));
|
||||
path, _lineCount, expectations, new Comment(match[3]!)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -264,8 +264,6 @@ class StatusFile {
|
|||
/// Throws a [SyntaxError] on the first found error.
|
||||
void validate(Environment environment) {
|
||||
for (var section in sections) {
|
||||
if (section.condition == null) continue;
|
||||
|
||||
var errors = <String>[];
|
||||
section.condition.validate(environment, errors);
|
||||
|
||||
|
@ -299,8 +297,8 @@ class StatusFile {
|
|||
class StatusSection {
|
||||
/// The expression that determines when this section is applied.
|
||||
///
|
||||
/// May be `null` for paths that appear before any section header in the file.
|
||||
/// In that case, the section always applies.
|
||||
/// Will be [Expression.always] for paths that appear before any section
|
||||
/// header in the file. In that case, the section always applies.
|
||||
final Expression condition;
|
||||
|
||||
/// The one-based line number where the section appears in the file.
|
||||
|
@ -311,8 +309,7 @@ class StatusSection {
|
|||
final List<Entry> sectionHeaderComments;
|
||||
|
||||
/// Returns true if this section should apply in the given [environment].
|
||||
bool isEnabled(Environment environment) =>
|
||||
condition == null || condition.evaluate(environment);
|
||||
bool isEnabled(Environment environment) => condition.evaluate(environment);
|
||||
|
||||
bool isEmpty() => !entries.any((entry) => entry is StatusEntry);
|
||||
|
||||
|
@ -322,8 +319,8 @@ class StatusSection {
|
|||
String toString() {
|
||||
var buffer = new StringBuffer();
|
||||
sectionHeaderComments.forEach(buffer.writeln);
|
||||
if (condition != null) {
|
||||
buffer.writeln("[ ${condition} ]");
|
||||
if (condition != Expression.always) {
|
||||
buffer.writeln("[ $condition ]");
|
||||
}
|
||||
entries.forEach(buffer.writeln);
|
||||
return buffer.toString();
|
||||
|
@ -336,10 +333,10 @@ class Comment {
|
|||
Comment(this._comment);
|
||||
|
||||
/// Returns the issue number embedded in [comment] or `null` if there is none.
|
||||
int issueNumber(String comment) {
|
||||
int? issueNumber(String comment) {
|
||||
var match = _issuePattern.firstMatch(comment);
|
||||
if (match == null) return null;
|
||||
return int.parse(match[1]);
|
||||
return int.parse(match[1]!);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -377,7 +374,7 @@ class CommentEntry extends Entry {
|
|||
class StatusEntry extends Entry {
|
||||
final String path;
|
||||
final List<Expectation> expectations;
|
||||
final Comment comment;
|
||||
final Comment? comment;
|
||||
|
||||
StatusEntry(this.path, lineNumber, this.expectations, this.comment)
|
||||
: super(lineNumber);
|
||||
|
|
|
@ -12,5 +12,5 @@ abstract class Environment {
|
|||
void validate(String name, String value, List<String> errors);
|
||||
|
||||
/// Looks up the value of the variable with [name].
|
||||
String lookUp(String name);
|
||||
String? lookUp(String name);
|
||||
}
|
||||
|
|
|
@ -206,17 +206,17 @@ class Expectation {
|
|||
}
|
||||
|
||||
final String _name;
|
||||
final Expectation _group;
|
||||
final Expectation? _group;
|
||||
|
||||
/// Whether this expectation is a test outcome. If not, it's a "meta marker".
|
||||
final bool isOutcome;
|
||||
|
||||
Expectation._(this._name, {Expectation group, bool isMeta: false})
|
||||
Expectation._(this._name, {Expectation? group, bool isMeta: false})
|
||||
: _group = group,
|
||||
isOutcome = !isMeta;
|
||||
|
||||
bool canBeOutcomeOf(Expectation expectation) {
|
||||
var outcome = this;
|
||||
Expectation? outcome = this;
|
||||
if (outcome == ignore) return true;
|
||||
|
||||
while (outcome != null) {
|
||||
|
|
|
@ -102,14 +102,14 @@ LogicExpression _minimizeByComplementation(LogicExpression expression) {
|
|||
});
|
||||
var combinedMinSets = _combineMinSets(
|
||||
clauses.map((e) => [new LogicExpression.and(e)]).toList(), []);
|
||||
List<List<Expression>> minCover = _findMinCover(combinedMinSets, []);
|
||||
List<List<LogicExpression>> minCover = _findMinCover(combinedMinSets, []);
|
||||
var finalOperands = minCover.map((minSet) => _reduceMinSet(minSet)).toList();
|
||||
return new LogicExpression.or(finalOperands).normalize();
|
||||
}
|
||||
|
||||
/// Computes all assignments of literals that make the [expression] evaluate to
|
||||
/// true.
|
||||
List<Expression> _satisfiableMinTerms(Expression expression) {
|
||||
List<Expression>? _satisfiableMinTerms(Expression expression) {
|
||||
var variables = _getVariables(expression);
|
||||
bool hasNotSatisfiableAssignment = false;
|
||||
List<Expression> satisfiableTerms = <Expression>[];
|
||||
|
@ -142,7 +142,7 @@ List<Expression> _satisfiableMinTerms(Expression expression) {
|
|||
/// which the [variables] was found.
|
||||
class TruthTableEnvironment extends Environment {
|
||||
final List<Expression> variables;
|
||||
int configuration;
|
||||
int configuration = -1;
|
||||
|
||||
TruthTableEnvironment(this.variables);
|
||||
|
||||
|
@ -172,10 +172,10 @@ class TruthTableEnvironment extends Environment {
|
|||
/// Combines [minSets] recursively as long as possible. Prime implicants (those
|
||||
/// that cannot be reduced further) are kept track of in [primeImplicants]. When
|
||||
/// finished the function returns all combined min sets.
|
||||
List<List<Expression>> _combineMinSets(
|
||||
List<List<Expression>> minSets, List<List<Expression>> primeImplicants) {
|
||||
List<List<LogicExpression>> _combineMinSets(List<List<LogicExpression>> minSets,
|
||||
List<List<LogicExpression>> primeImplicants) {
|
||||
List<List<LogicExpression>> combined = <List<LogicExpression>>[];
|
||||
var addedInThisIteration = new Set<List<Expression>>();
|
||||
var addedInThisIteration = new Set<List<LogicExpression>>();
|
||||
for (var i = 0; i < minSets.length; i++) {
|
||||
var minSet = minSets[i];
|
||||
var combinedMinSet = false;
|
||||
|
@ -262,8 +262,9 @@ LogicExpression _reduceMinSet(List<LogicExpression> minSet) {
|
|||
/// minimum set cover is NP-hard, and we are not trying to be really cleaver
|
||||
/// here. The implicants that cover only a single truth assignment can be
|
||||
/// directly added to [cover].
|
||||
List<List<Expression>> _findMinCover(
|
||||
List<List<Expression>> primaryImplicants, List<List<Expression>> cover) {
|
||||
List<List<LogicExpression>> _findMinCover(
|
||||
List<List<LogicExpression>> primaryImplicants,
|
||||
List<List<LogicExpression>> cover) {
|
||||
var minCover = primaryImplicants.toList()..addAll(cover);
|
||||
if (cover.isEmpty) {
|
||||
var allImplicants = primaryImplicants.toList();
|
||||
|
@ -332,9 +333,9 @@ List<Expression> _difference(List<Expression> As, List<Expression> Bs) {
|
|||
|
||||
/// Finds the first occurrence of [expressionToFind] in [expressions] or
|
||||
/// returns null.
|
||||
Expression _findFirst<Expression>(
|
||||
Expression? _findFirst<Expression>(
|
||||
expressionToFind, List<Expression> expressions) {
|
||||
return expressions.firstWhere(
|
||||
return expressions.cast<Expression?>().firstWhere(
|
||||
(otherExpression) => expressionToFind.compareTo(otherExpression) == 0,
|
||||
orElse: () => null);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ import '../environment.dart';
|
|||
|
||||
/// A parsed Boolean expression AST.
|
||||
abstract class Expression implements Comparable<Expression> {
|
||||
/// An expression that always evaluates to true.
|
||||
///
|
||||
/// Used for the section at the top of a status file with no expression.
|
||||
static const Expression always = _AlwaysExpression();
|
||||
|
||||
/// Parses Boolean expressions in a .status file for Dart.
|
||||
///
|
||||
/// The grammar is:
|
||||
|
@ -26,6 +31,8 @@ abstract class Expression implements Comparable<Expression> {
|
|||
static Expression parse(String expression) =>
|
||||
new _ExpressionParser(expression).parse();
|
||||
|
||||
const Expression();
|
||||
|
||||
/// Validates that this expression does not contain any invalid uses of
|
||||
/// variables.
|
||||
///
|
||||
|
@ -111,6 +118,29 @@ class Variable {
|
|||
}
|
||||
}
|
||||
|
||||
/// An expression that always evaluates to true.
|
||||
///
|
||||
/// Used for the implicit section at the top of a status file that always
|
||||
/// matches.
|
||||
class _AlwaysExpression extends Expression {
|
||||
const _AlwaysExpression();
|
||||
|
||||
@override
|
||||
int _compareToMyType(covariant _AlwaysExpression other) => 0;
|
||||
|
||||
@override
|
||||
int get _typeComparison => 0;
|
||||
|
||||
@override
|
||||
bool evaluate(Environment environment) => true;
|
||||
|
||||
@override
|
||||
Expression normalize() => this;
|
||||
|
||||
@override
|
||||
void validate(Environment environment, List<String> errors) {}
|
||||
}
|
||||
|
||||
/// Tests whether a given variable is or is not equal some literal value, as in:
|
||||
/// ```
|
||||
/// $variable == someValue
|
||||
|
@ -156,7 +186,7 @@ class ComparisonExpression extends Expression {
|
|||
|
||||
// Comparisons come before variables so that "$compiler == ..." and
|
||||
// "$runtime == ..." appear on the left in status expressions.
|
||||
int get _typeComparison => 0;
|
||||
int get _typeComparison => 1;
|
||||
|
||||
String toString() => "\$${left.name} ${negate ? '!=' : '=='} $right";
|
||||
}
|
||||
|
@ -203,7 +233,7 @@ class VariableExpression extends Expression {
|
|||
return _compareBool(negate, other.negate);
|
||||
}
|
||||
|
||||
int get _typeComparison => 1;
|
||||
int get _typeComparison => 2;
|
||||
|
||||
String toString() => "${negate ? "!" : ""}\$${variable.name}";
|
||||
}
|
||||
|
@ -237,7 +267,7 @@ class LogicExpression extends Expression {
|
|||
}
|
||||
}
|
||||
|
||||
Expression normalize() {
|
||||
LogicExpression normalize() {
|
||||
// Normalize the order of the clauses. Since there is no short-circuiting,
|
||||
// a || b means the same as b || a. Picking a standard order lets us
|
||||
// identify and collapse identical expressions that only differ by clause
|
||||
|
@ -281,7 +311,7 @@ class LogicExpression extends Expression {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int get _typeComparison => 2;
|
||||
int get _typeComparison => 3;
|
||||
|
||||
String toString() {
|
||||
String parenthesize(Expression operand) {
|
||||
|
@ -365,7 +395,7 @@ class _ExpressionParser {
|
|||
"Expected identifier in expression, got ${_scanner.current}");
|
||||
}
|
||||
|
||||
var left = new Variable(_scanner.current);
|
||||
var left = new Variable(_scanner.current!);
|
||||
_scanner.advance();
|
||||
|
||||
if (!negate &&
|
||||
|
@ -378,7 +408,7 @@ class _ExpressionParser {
|
|||
"Expected value in expression, got ${_scanner.current}");
|
||||
}
|
||||
|
||||
var right = _scanner.advance();
|
||||
var right = _scanner.advance()!;
|
||||
return new ComparisonExpression(left, right, isNotEquals);
|
||||
} else {
|
||||
return new VariableExpression(left, negate: negate);
|
||||
|
@ -401,7 +431,7 @@ class _Scanner {
|
|||
/// The token strings being iterated.
|
||||
final Iterator<String> tokenIterator;
|
||||
|
||||
String current;
|
||||
String? current;
|
||||
|
||||
_Scanner(String expression) : tokenIterator = tokenize(expression).iterator {
|
||||
advance();
|
||||
|
@ -414,7 +444,7 @@ class _Scanner {
|
|||
|
||||
return _tokenPattern
|
||||
.allMatches(expression)
|
||||
.map((match) => match[0])
|
||||
.map((match) => match[0]!)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
@ -424,7 +454,7 @@ class _Scanner {
|
|||
// All non-identifier tokens are one or two characters,
|
||||
// so a longer token must be an identifier.
|
||||
bool get isIdentifier =>
|
||||
current.length > 2 || _identifierPattern.hasMatch(current);
|
||||
current!.length > 2 || _identifierPattern.hasMatch(current!);
|
||||
|
||||
/// If the current token is [token], consumes it and returns `true`.
|
||||
bool match(String token) {
|
||||
|
@ -435,7 +465,7 @@ class _Scanner {
|
|||
}
|
||||
|
||||
/// Consumes the current token and returns it.
|
||||
String advance() {
|
||||
String? advance() {
|
||||
var previous = current;
|
||||
current = tokenIterator.moveNext() ? tokenIterator.current : null;
|
||||
return previous;
|
||||
|
|
|
@ -45,7 +45,7 @@ final _issuePattern = new RegExp(r"[Ii]ssue (\d+)");
|
|||
class StatusFile {
|
||||
final String path;
|
||||
final List<StatusSection> sections = [];
|
||||
final List<String> _comments = [];
|
||||
final List<String?> _comments = [];
|
||||
|
||||
int _lineCount = 0;
|
||||
|
||||
|
@ -58,13 +58,10 @@ class StatusFile {
|
|||
var lines = new File(path).readAsLinesSync();
|
||||
_comments.length = lines.length + 1;
|
||||
|
||||
// The current section whose rules are being parsed.
|
||||
StatusSection section;
|
||||
|
||||
for (var line in lines) {
|
||||
_lineCount++;
|
||||
|
||||
fail(String message, [List<String> errors]) {
|
||||
fail(String message, [List<String>? errors]) {
|
||||
throw new SyntaxError(_shortPath, _lineCount, line, message, errors);
|
||||
}
|
||||
|
||||
|
@ -86,9 +83,8 @@ class StatusFile {
|
|||
var match = _sectionPattern.firstMatch(source);
|
||||
if (match != null) {
|
||||
try {
|
||||
var condition = Expression.parse(match[1].trim());
|
||||
section = new StatusSection(condition, _lineCount);
|
||||
sections.add(section);
|
||||
var condition = Expression.parse(match[1]!.trim());
|
||||
sections.add(new StatusSection(condition, _lineCount));
|
||||
} on FormatException {
|
||||
fail("Status expression syntax error");
|
||||
}
|
||||
|
@ -98,10 +94,10 @@ class StatusFile {
|
|||
// Otherwise, it should be a new entry under the current section.
|
||||
match = _entryPattern.firstMatch(source);
|
||||
if (match != null) {
|
||||
var path = match[1].trim();
|
||||
var path = match[1]!.trim();
|
||||
// TODO(whesse): Handle test names ending in a wildcard (*).
|
||||
var expectations = <Expectation>[];
|
||||
for (var name in match[2].split(",")) {
|
||||
for (var name in match[2]!.split(",")) {
|
||||
name = name.trim();
|
||||
try {
|
||||
expectations.add(Expectation.find(name));
|
||||
|
@ -114,12 +110,11 @@ class StatusFile {
|
|||
|
||||
// If we haven't found a section header yet, create an implicit section
|
||||
// that matches everything.
|
||||
if (section == null) {
|
||||
section = new StatusSection(null, -1);
|
||||
sections.add(section);
|
||||
if (sections.isEmpty) {
|
||||
sections.add(new StatusSection(Expression.always, -1));
|
||||
}
|
||||
|
||||
section.entries
|
||||
sections.last.entries
|
||||
.add(new StatusEntry(path, _lineCount, expectations, issue));
|
||||
continue;
|
||||
}
|
||||
|
@ -138,8 +133,6 @@ class StatusFile {
|
|||
// TODO(rnystrom): It would be more useful if it reported all of the errors
|
||||
// instead of stopping on the first.
|
||||
for (var section in sections) {
|
||||
if (section.condition == null) continue;
|
||||
|
||||
var errors = <String>[];
|
||||
section.condition.validate(environment, errors);
|
||||
|
||||
|
@ -158,11 +151,11 @@ class StatusFile {
|
|||
}
|
||||
|
||||
/// Returns the issue number embedded in [comment] or `null` if there is none.
|
||||
int _issueNumber(String comment) {
|
||||
int? _issueNumber(String comment) {
|
||||
var match = _issuePattern.firstMatch(comment);
|
||||
if (match == null) return null;
|
||||
|
||||
return int.parse(match[1]);
|
||||
return int.parse(match[1]!);
|
||||
}
|
||||
|
||||
String toString() {
|
||||
|
@ -192,7 +185,7 @@ class StatusFile {
|
|||
var lastLine = 0;
|
||||
var needBlankLine = false;
|
||||
|
||||
void writeLine(String text, int line) {
|
||||
void writeLine(String? text, int line) {
|
||||
var comment = _comments[line];
|
||||
if (text == null && comment == null) {
|
||||
// There's no comment on this line, so it's blank.
|
||||
|
@ -216,17 +209,15 @@ class StatusFile {
|
|||
}
|
||||
|
||||
void writeText(String text, int line) {
|
||||
if (line != null) {
|
||||
while (++lastLine < line) {
|
||||
writeLine(null, lastLine);
|
||||
}
|
||||
while (++lastLine < line) {
|
||||
writeLine(null, lastLine);
|
||||
}
|
||||
|
||||
writeLine(text, line);
|
||||
}
|
||||
|
||||
for (var section in sections) {
|
||||
if (section.condition != null) {
|
||||
if (section.condition != Expression.always) {
|
||||
writeText("[ ${section.condition} ]", section.lineNumber);
|
||||
}
|
||||
|
||||
|
@ -254,8 +245,8 @@ class StatusFile {
|
|||
class StatusSection {
|
||||
/// The expression that determines when this section is applied.
|
||||
///
|
||||
/// May be `null` for paths that appear before any section header in the file.
|
||||
/// In that case, the section always applies.
|
||||
/// Will be [Expression.always] for paths that appear before any section
|
||||
/// header in the file. In that case, the section always applies.
|
||||
final Expression condition;
|
||||
|
||||
/// The one-based line number where the entry appears in the file.
|
||||
|
@ -264,8 +255,7 @@ class StatusSection {
|
|||
final List<StatusEntry> entries = [];
|
||||
|
||||
/// Returns true if this section should apply in the given [environment].
|
||||
bool isEnabled(Environment environment) =>
|
||||
condition == null || condition.evaluate(environment);
|
||||
bool isEnabled(Environment environment) => condition.evaluate(environment);
|
||||
|
||||
StatusSection(this.condition, this.lineNumber);
|
||||
}
|
||||
|
@ -278,7 +268,7 @@ class StatusEntry {
|
|||
final int lineNumber;
|
||||
|
||||
final List<Expectation> expectations;
|
||||
final int issue;
|
||||
final int? issue;
|
||||
|
||||
StatusEntry(this.path, this.lineNumber, this.expectations, this.issue);
|
||||
}
|
||||
|
@ -289,7 +279,7 @@ class SyntaxError implements Exception {
|
|||
final int lineNumber;
|
||||
final String line;
|
||||
final String message;
|
||||
final List<String> errors;
|
||||
final List<String>? errors;
|
||||
|
||||
SyntaxError(this.file, this.lineNumber, this.line, this.message, this.errors);
|
||||
|
||||
|
@ -298,10 +288,8 @@ class SyntaxError implements Exception {
|
|||
buffer.writeln('$message in "$file" line $lineNumber:');
|
||||
buffer.writeln(line);
|
||||
|
||||
if (errors != null) {
|
||||
for (var error in errors) {
|
||||
buffer.writeln("- ${error.replaceAll('\n', '\n ')}");
|
||||
}
|
||||
for (var error in errors ?? const []) {
|
||||
buffer.writeln("- ${error.replaceAll('\n', '\n ')}");
|
||||
}
|
||||
|
||||
return buffer.toString().trimRight();
|
||||
|
|
|
@ -115,10 +115,9 @@ Iterable<LintingError> lintAlphabeticalOrderingOfPaths(StatusSection section) {
|
|||
|
||||
/// Checks that each section expression have been normalized.
|
||||
Iterable<LintingError> lintNormalizedSection(StatusSection section) {
|
||||
if (section.condition == null) return const [];
|
||||
var nonNormalized = section.condition.toString();
|
||||
var normalized = section.condition.normalize().toString();
|
||||
if (section.condition.toString() != normalized) {
|
||||
if (nonNormalized != normalized) {
|
||||
return [
|
||||
new LintingError(
|
||||
section.lineNumber,
|
||||
|
@ -184,11 +183,11 @@ Iterable<LintingError> lintSectionHeaderOrdering(List<StatusSection> sections) {
|
|||
if (witness != null) {
|
||||
return [
|
||||
new LintingError(
|
||||
witness.second.lineNumber,
|
||||
witness.second!.lineNumber,
|
||||
"Section expressions are not correctly ordered in file. "
|
||||
"'${witness.first.condition}' on line ${witness.first.lineNumber} "
|
||||
"should come before '${witness.second.condition}' at line "
|
||||
"${witness.second.lineNumber}.")
|
||||
"'${witness.first!.condition}' on line ${witness.first!.lineNumber} "
|
||||
"should come before '${witness.second!.condition}' at line "
|
||||
"${witness.second!.lineNumber}.")
|
||||
];
|
||||
}
|
||||
return [];
|
||||
|
@ -198,14 +197,12 @@ Iterable<LintingError> lintSectionHeaderOrdering(List<StatusSection> sections) {
|
|||
Iterable<LintingError> lintSectionHeaderDuplicates(
|
||||
List<StatusSection> sections) {
|
||||
var errors = <LintingError>[];
|
||||
var sorted = sections.where((section) => section.condition != null).toList()
|
||||
var sorted = sections.toList()
|
||||
..sort((a, b) => a.condition.compareTo(b.condition));
|
||||
for (var i = 1; i < sorted.length; i++) {
|
||||
var section = sorted[i];
|
||||
var previousSection = sorted[i - 1];
|
||||
if (section.condition != null &&
|
||||
previousSection.condition != null &&
|
||||
section.condition.compareTo(previousSection.condition) == 0) {
|
||||
if (section.condition.compareTo(previousSection.condition) == 0) {
|
||||
errors.add(new LintingError(
|
||||
section.lineNumber,
|
||||
"The condition "
|
||||
|
@ -216,7 +213,7 @@ Iterable<LintingError> lintSectionHeaderDuplicates(
|
|||
return errors;
|
||||
}
|
||||
|
||||
ListNotEqualWitness<T> _findNotEqualWitness<T>(List<T> first, List<T> second) {
|
||||
ListNotEqualWitness<T>? _findNotEqualWitness<T>(List<T> first, List<T> second) {
|
||||
if (first.isEmpty && second.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
@ -233,7 +230,7 @@ ListNotEqualWitness<T> _findNotEqualWitness<T>(List<T> first, List<T> second) {
|
|||
}
|
||||
|
||||
class ListNotEqualWitness<T> {
|
||||
final T first;
|
||||
final T second;
|
||||
final T? first;
|
||||
final T? second;
|
||||
ListNotEqualWitness(this.first, this.second);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:status_file/src/expression.dart';
|
||||
|
||||
import 'canonical_status_file.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
|
@ -52,10 +54,9 @@ StatusFile _sortSectionsAndCombine(StatusFile statusFile) {
|
|||
List<StatusSection> newSections = [];
|
||||
// Copy over all sections and normalize all the expressions.
|
||||
oldStatusFile.sections.forEach((section) {
|
||||
if (section.condition != null && section.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (section.condition != null) {
|
||||
if (section.condition != Expression.always) {
|
||||
if (section.isEmpty()) return;
|
||||
|
||||
newSections.add(new StatusSection(section.condition.normalize(),
|
||||
section.lineNumber, section.sectionHeaderComments)
|
||||
..entries.addAll(section.entries));
|
||||
|
@ -63,23 +64,17 @@ StatusFile _sortSectionsAndCombine(StatusFile statusFile) {
|
|||
newSections.add(section);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort the headers
|
||||
newSections.sort((a, b) {
|
||||
if (a.condition == null) {
|
||||
return -1;
|
||||
} else if (b.condition == null) {
|
||||
return 1;
|
||||
}
|
||||
return a.condition.compareTo(b.condition);
|
||||
});
|
||||
newSections.sort((a, b) => a.condition.compareTo(b.condition));
|
||||
|
||||
// See if we can combine section headers by simple comparison.
|
||||
StatusFile newStatusFile = new StatusFile(statusFile.path);
|
||||
var newStatusFile = new StatusFile(statusFile.path);
|
||||
newStatusFile.sections.add(newSections[0]);
|
||||
for (var i = 1; i < newSections.length; i++) {
|
||||
var previousSection = newSections[i - 1];
|
||||
var currentSection = newSections[i];
|
||||
if (previousSection.condition != null &&
|
||||
previousSection.condition.compareTo(currentSection.condition) == 0) {
|
||||
if (previousSection.condition.compareTo(currentSection.condition) == 0) {
|
||||
newStatusFile.sections.last.entries.addAll(currentSection.entries);
|
||||
} else {
|
||||
newStatusFile.sections.add(currentSection);
|
||||
|
|
|
@ -2,7 +2,7 @@ name: status_file
|
|||
# This package is not intended for consumption on pub.dev. DO NOT publish.
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: "^2.3.0"
|
||||
sdk: "^2.12.0"
|
||||
dependencies:
|
||||
path: "^1.4.0"
|
||||
args: "^1.4.4"
|
||||
|
|
|
@ -100,7 +100,7 @@ void testCheckForDisjunctions_notAllowedDisjunction() {
|
|||
vm/tests: Skip # this comment is valid
|
||||
""",
|
||||
"Error at line 1: Expression contains '||'. Please split the expression "
|
||||
"into multiple separate sections.",
|
||||
"into multiple separate sections.",
|
||||
disjunctions: true);
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ vm/tests: Skip # this should come after a_test
|
|||
a_test: Pass
|
||||
""",
|
||||
"Error at line 1: Test paths are not alphabetically ordered in "
|
||||
"section. a_test should come before vm/tests.");
|
||||
"section. a_test should come before vm/tests.");
|
||||
}
|
||||
|
||||
void testCheckForAlphabeticalOrderingOfPaths_okOrdering() {
|
||||
|
@ -139,7 +139,7 @@ bc_test: Pass
|
|||
xyz_test: Skip
|
||||
""",
|
||||
"Error at line 1: The status entry 'a_test: Pass' is duplicated on lines "
|
||||
"2 and 3.");
|
||||
"2 and 3.");
|
||||
}
|
||||
|
||||
void testCheckForCorrectOrderingInSections_invalidRuntimeBeforeCompiler() {
|
||||
|
@ -148,7 +148,7 @@ void testCheckForCorrectOrderingInSections_invalidRuntimeBeforeCompiler() {
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 1: Condition expression should be '$compiler == dart2js "
|
||||
r"&& $runtime == ff' but was '$runtime == ff && $compiler == dart2js'.");
|
||||
r"&& $runtime == ff' but was '$runtime == ff && $compiler == dart2js'.");
|
||||
}
|
||||
|
||||
void testCheckForCorrectOrderingInSections_invalidRuntimeBeforeMode() {
|
||||
|
@ -157,7 +157,7 @@ void testCheckForCorrectOrderingInSections_invalidRuntimeBeforeMode() {
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 1: Condition expression should be '$mode == debug && "
|
||||
r"$runtime == ff' but was '$runtime == ff && $mode == debug'.");
|
||||
r"$runtime == ff' but was '$runtime == ff && $mode == debug'.");
|
||||
}
|
||||
|
||||
void testCheckForCorrectOrderingInSections_invalidSystemBeforeMode() {
|
||||
|
@ -166,7 +166,7 @@ void testCheckForCorrectOrderingInSections_invalidSystemBeforeMode() {
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 1: Condition expression should be '$mode == debug && "
|
||||
r"$system == win' but was '$system == win && $mode == debug'.");
|
||||
r"$system == win' but was '$system == win && $mode == debug'.");
|
||||
}
|
||||
|
||||
void testCheckForCorrectOrderingInSections_invalidStrongBeforeKernel() {
|
||||
|
@ -175,7 +175,7 @@ void testCheckForCorrectOrderingInSections_invalidStrongBeforeKernel() {
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 1: Condition expression should be '!$kernel && !$strong' "
|
||||
r"but was '!$strong && !$kernel'.");
|
||||
r"but was '!$strong && !$kernel'.");
|
||||
}
|
||||
|
||||
void testCheckForCorrectOrderingInSections_invalidOrdering() {
|
||||
|
@ -184,8 +184,8 @@ void testCheckForCorrectOrderingInSections_invalidOrdering() {
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 1: Condition expression should be '$builder_tag == "
|
||||
r"strong && $compiler == dart2js && !$browser' but was "
|
||||
r"'$compiler == dart2js && $builder_tag == strong && !$browser'.");
|
||||
r"strong && $compiler == dart2js && !$browser' but was "
|
||||
r"'$compiler == dart2js && $builder_tag == strong && !$browser'.");
|
||||
}
|
||||
|
||||
void testCheckForCorrectOrderingInSections_okOrdering() {
|
||||
|
@ -203,8 +203,8 @@ a_test: Pass
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 1: Section expressions are not correctly ordered in file."
|
||||
r" '$compiler == dart2js' on line 4 should come before '$runtime == ff' "
|
||||
r"at line 1.");
|
||||
r" '$compiler == dart2js' on line 4 should come before '$runtime == ff' "
|
||||
r"at line 1.");
|
||||
}
|
||||
|
||||
void checkLintNormalizedSection_invalidAlphabeticalOrderingVariableArguments() {
|
||||
|
@ -216,8 +216,8 @@ a_test: Pass
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 1: Section expressions are not correctly ordered in file."
|
||||
r" '$runtime == chrome' on line 4 should come before '$runtime == ff' at "
|
||||
r"line 1.");
|
||||
r" '$runtime == chrome' on line 4 should come before '$runtime == ff' at "
|
||||
r"line 1.");
|
||||
}
|
||||
|
||||
void checkLintNormalizedSection_invalidOrderingWithNotEqual() {
|
||||
|
@ -233,8 +233,8 @@ a_test: Pass
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 4: Section expressions are not correctly ordered in file."
|
||||
r" '$runtime == ff' on line 7 should come before '$runtime != ff' at "
|
||||
r"line 4.");
|
||||
r" '$runtime == ff' on line 7 should come before '$runtime != ff' at "
|
||||
r"line 4.");
|
||||
}
|
||||
|
||||
void checkLintNormalizedSection_invalidOrderingWithNegation() {
|
||||
|
@ -251,7 +251,7 @@ a_test: Pass
|
|||
|
||||
""",
|
||||
r"Error at line 4: Section expressions are not correctly ordered in file."
|
||||
r" '$checked' on line 7 should come before '!$checked' at line 4.");
|
||||
r" '$checked' on line 7 should come before '!$checked' at line 4.");
|
||||
}
|
||||
|
||||
void checkLintNormalizedSection_correctOrdering() {
|
||||
|
@ -285,5 +285,5 @@ a_test: Pass
|
|||
a_test: Pass
|
||||
""",
|
||||
r"Error at line 4: The condition '!$browser' is duplicated on lines 1 "
|
||||
r"and 4.");
|
||||
r"and 4.");
|
||||
}
|
||||
|
|
|
@ -69,8 +69,8 @@ void checkSemanticallyEqual(StatusFile original, StatusFile normalized,
|
|||
"$entriesInNormalized. Those two numbers are not the same.");
|
||||
}
|
||||
for (var section in original.sections) {
|
||||
section.entries.where((entry) => entry is StatusEntry).forEach((entry) =>
|
||||
findInStatusFile(normalized, entry, section.condition?.normalize(),
|
||||
section.entries.whereType<StatusEntry>().forEach((entry) =>
|
||||
findInStatusFile(normalized, entry, section.condition.normalize(),
|
||||
warnOnDuplicateHeader: warnOnDuplicateHeader));
|
||||
}
|
||||
}
|
||||
|
@ -87,12 +87,7 @@ void findInStatusFile(
|
|||
{bool warnOnDuplicateHeader = false}) {
|
||||
int foundEntryPosition = -1;
|
||||
for (var section in statusFile.sections) {
|
||||
if (section.condition == null && condition != null ||
|
||||
section.condition != null && condition == null) {
|
||||
continue;
|
||||
}
|
||||
if (section.condition != null &&
|
||||
section.condition.normalize().compareTo(condition) != 0) {
|
||||
if (section.condition.normalize().compareTo(condition) != 0) {
|
||||
continue;
|
||||
}
|
||||
var matchingEntries = section.entries
|
||||
|
|
|
@ -19,8 +19,8 @@ void main() {
|
|||
if (!entry.path.endsWith(".status")) continue;
|
||||
try {
|
||||
new StatusFile.read(entry.path);
|
||||
} catch (err) {
|
||||
Expect.fail("Could not parse '${entry.path}'.\n$err");
|
||||
} catch (err, stack) {
|
||||
Expect.fail("Could not parse '${entry.path}'.\n$err\n$stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,13 +55,13 @@ void testDnf() {
|
|||
// https://en.wikipedia.org/wiki/Quine%E2%80%93McCluskey_algorithm
|
||||
shouldDnfTo(
|
||||
r"$a && !$b && !$c && !$d || $a && !$b && !$c && $d || "
|
||||
r"$a && !$b && $c && !$d || $a && !$b && $c && $d",
|
||||
r"$a && !$b && $c && !$d || $a && !$b && $c && $d",
|
||||
r"$a && !$b");
|
||||
|
||||
shouldDnfTo(
|
||||
r"!$a && $b && !$c && !$d || $a && !$b && !$c && !$d || "
|
||||
r"$a && !$b && $c && !$d || $a && !$b && $c && $d || $a && $b && !$c && !$d ||"
|
||||
r" $a && $b && $c && $d || $a && !$b && !$c && $d || $a && $b && $c && !$d",
|
||||
r"$a && !$b && $c && !$d || $a && !$b && $c && $d || $a && $b && !$c && !$d ||"
|
||||
r" $a && $b && $c && $d || $a && !$b && !$c && $d || $a && $b && $c && !$d",
|
||||
r"$a && !$b || $a && $c || $b && !$c && !$d");
|
||||
|
||||
// Test that an expression is converted to dnf and minified correctly.
|
||||
|
|
|
@ -17,7 +17,7 @@ class TestEnvironment implements Environment {
|
|||
}
|
||||
|
||||
/// Looks up the value of the variable with [name].
|
||||
String lookUp(String name) => _values[name];
|
||||
String? lookUp(String name) => _values[name];
|
||||
|
||||
operator []=(String key, String value) => _values[key] = value;
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ main() {
|
|||
}
|
||||
|
||||
void testExpression() {
|
||||
var expression = Expression
|
||||
.parse(r" $mode == debug && ($arch == chromium || $arch == dartc) ");
|
||||
var expression = Expression.parse(
|
||||
r" $mode == debug && ($arch == chromium || $arch == dartc) ");
|
||||
Expect.equals(r"$mode == debug && ($arch == chromium || $arch == dartc)",
|
||||
expression.toString());
|
||||
|
||||
|
|
Loading…
Reference in a new issue