Skip to content

Commit 7320da0

Browse files
srawlinsCommit Queue
authored and
Commit Queue
committed
linter: Add a PRESUBMIT check for example/all.yaml
Work towards #53578 Change-Id: Ia07d999abc2fcf4b8195c9f7688799bc099a1d88 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/341385 Reviewed-by: Phil Quitslund <[email protected]> Reviewed-by: Alexander Thomas <[email protected]> Reviewed-by: Jonas Termansen <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent b305954 commit 7320da0

File tree

8 files changed

+182
-91
lines changed

8 files changed

+182
-91
lines changed

PRESUBMIT.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,14 +298,13 @@ def _CheckClangTidy(input_api, output_api):
298298
def _CheckAnalyzerFiles(input_api, output_api):
299299
"""Run analyzer checks on source files."""
300300

301-
# The first (and so far, only) check, is to verify the "error fix status"
302-
# file.
303-
relevant_files = [
301+
# Verify the "error fix status" file.
302+
code_files = [
304303
"pkg/analyzer/lib/src/error/error_code_values.g.dart",
305304
"pkg/linter/lib/src/rules.dart",
306305
]
307306

308-
if any(f.LocalPath() in relevant_files for f in input_api.AffectedFiles()):
307+
if any(f.LocalPath() in code_files for f in input_api.AffectedFiles()):
309308
args = [
310309
"tools/sdks/dart-sdk/bin/dart",
311310
"pkg/analysis_server/tool/presubmit/verify_error_fix_status.dart",
@@ -320,6 +319,23 @@ def _CheckAnalyzerFiles(input_api, output_api):
320319
long_text=stdout)
321320
]
322321

322+
# Verify the linter's `example/all.yaml` file.
323+
if any(f.LocalPath().startswith('pkg/linter/lib/src/rules')
324+
for f in input_api.AffectedFiles()):
325+
args = [
326+
"tools/sdks/dart-sdk/bin/dart",
327+
"pkg/analysis_server/tool/checks/check_all_yaml.dart",
328+
]
329+
stdout = input_api.subprocess.check_output(args).strip()
330+
if not stdout:
331+
return []
332+
333+
return [
334+
output_api.PresubmitError(
335+
"The check_all_yaml linter tool revealed issues:",
336+
long_text=stdout)
337+
]
338+
323339
# TODO(srawlins): Check more:
324340
# * "verify_sorted" for individual modified (not deleted) files in
325341
# Analyzer-team-owned directories.

pkg/linter/test/integration_test.dart

Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,13 @@
55
import 'dart:io';
66

77
import 'package:analyzer/src/lint/io.dart';
8-
import 'package:analyzer/src/lint/state.dart';
9-
import 'package:linter/src/analyzer.dart';
10-
import 'package:linter/src/rules.dart';
11-
import 'package:linter/src/utils.dart';
128
import 'package:test/test.dart';
13-
import 'package:yaml/yaml.dart';
149

10+
import '../tool/checks/check_all_yaml.dart';
1511
import 'mocks.dart';
16-
import 'test_constants.dart';
1712

1813
void main() {
19-
// ignore: unnecessary_lambdas
2014
group('integration', () {
21-
coreTests();
22-
});
23-
}
24-
25-
void coreTests() {
26-
group('core', () {
2715
group('config', () {
2816
var currentOut = outSink;
2917
var collectingOut = CollectingSink();
@@ -40,61 +28,11 @@ void coreTests() {
4028

4129
group('examples', () {
4230
test('all.yaml', () {
43-
var src = readFile(pathRelativeToPackageRoot(['example', 'all.yaml']));
44-
45-
var options = _getOptionsFromString(src);
46-
var configuredLints =
47-
// ignore: cast_nullable_to_non_nullable
48-
(options['linter'] as YamlMap)['rules'] as YamlList;
49-
50-
// rules are sorted
51-
expect(
52-
configuredLints, orderedEquals(configuredLints.toList()..sort()));
53-
54-
registerLintRules();
55-
56-
var registered = Analyzer.facade.registeredRules
57-
.where((r) =>
58-
!r.state.isDeprecated &&
59-
!r.state.isInternal &&
60-
!r.state.isRemoved)
61-
.map((r) => r.name);
62-
63-
for (var l in configuredLints) {
64-
if (!registered.contains(l)) {
65-
printToConsole(l);
66-
}
31+
var errors = checkAllYaml();
32+
if (errors != null) {
33+
fail(errors);
6734
}
68-
69-
expect(configuredLints, unorderedEquals(registered));
7035
});
7136
});
7237
});
7338
}
74-
75-
/// Provide the options found in [optionsSource].
76-
Map<String, YamlNode> _getOptionsFromString(String optionsSource) {
77-
var options = <String, YamlNode>{};
78-
var doc = loadYamlNode(optionsSource);
79-
80-
// Empty options.
81-
if (doc is YamlScalar && doc.value == null) {
82-
return options;
83-
}
84-
if (doc is! YamlMap) {
85-
throw Exception(
86-
'Bad options file format (expected map, got ${doc.runtimeType})');
87-
}
88-
doc.nodes.forEach((k, YamlNode v) {
89-
Object? key;
90-
if (k is YamlScalar) {
91-
key = k.value;
92-
}
93-
if (key is! String) {
94-
throw Exception('Bad options file format (expected String scope key, '
95-
'got ${k.runtimeType})');
96-
}
97-
options[key] = v;
98-
});
99-
return options;
100-
}

pkg/linter/test/test_constants.dart

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,11 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:io';
6-
7-
import 'package:path/path.dart' as path;
5+
import '../tool/util/path_utils.dart';
86

97
final String integrationTestDir =
108
pathRelativeToPackageRoot(['test_data', 'integration']);
119
final String ruleTestDataDir =
1210
pathRelativeToPackageRoot(['test_data', 'rules']);
1311
final String ruleTestDir = pathRelativeToPackageRoot(['test', 'rules']);
1412
final String testConfigDir = pathRelativeToPackageRoot(['test', 'configs']);
15-
16-
List<String> get _scriptPathParts =>
17-
path.split(path.dirname(path.fromUri(Platform.script.path)));
18-
19-
String pathRelativeToPackageRoot(Iterable<String> parts) {
20-
var pathParts = _scriptPathParts;
21-
pathParts.replaceRange(pathParts.length - 1, pathParts.length, parts);
22-
return path.joinAll(pathParts);
23-
}
24-
25-
String pathRelativeToPkgDir(Iterable<String> parts) {
26-
var pathParts = _scriptPathParts;
27-
pathParts.replaceRange(pathParts.length - 2, pathParts.length, parts);
28-
return path.joinAll(pathParts);
29-
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:analyzer/src/lint/io.dart';
8+
import 'package:analyzer/src/lint/state.dart';
9+
import 'package:linter/src/analyzer.dart';
10+
import 'package:linter/src/rules.dart';
11+
import 'package:yaml/yaml.dart';
12+
13+
import '../util/path_utils.dart';
14+
15+
/// Checks the 'example/all.yaml' file for correctness.
16+
///
17+
/// Prints any errors.
18+
void main() {
19+
var errors = checkAllYaml();
20+
if (errors != null) {
21+
// ignore: avoid_print
22+
print(errors);
23+
exitCode = 1;
24+
}
25+
}
26+
27+
/// Checks the 'example/all.yaml' file for correctness, returning a String if
28+
/// there are errors, and `null` otherwise.
29+
String? checkAllYaml() {
30+
var allYamlPath = pathRelativeToPackageRoot(['example', 'all.yaml']);
31+
var src = readFile(allYamlPath);
32+
33+
var options = _getOptionsFromString(src);
34+
var linterSection = options['linter'] as YamlMap?;
35+
if (linterSection == null) {
36+
return "Error: '$allYamlPath' does not have a 'linter' section.";
37+
}
38+
39+
var configuredRules = (linterSection['rules'] as YamlList?)?.cast<String>();
40+
if (configuredRules == null) {
41+
return "Error: '$allYamlPath' does not have a 'rules' section.";
42+
}
43+
44+
var sortedRules = configuredRules.toList()..sort();
45+
46+
for (var i = 0; i < configuredRules.length; i++) {
47+
if (configuredRules[i] != sortedRules[i]) {
48+
return "Error: Rules in '$allYamlPath' are not sorted alphabetically, "
49+
"starting at '${configuredRules[i]}'.";
50+
}
51+
}
52+
53+
registerLintRules();
54+
55+
var registeredRules = Analyzer.facade.registeredRules
56+
.where((r) =>
57+
!r.state.isDeprecated && !r.state.isInternal && !r.state.isRemoved)
58+
.map((r) => r.name);
59+
60+
var extraRules = <String>[];
61+
var missingRules = <String>[];
62+
63+
for (var rule in configuredRules) {
64+
if (!registeredRules.contains(rule)) {
65+
extraRules.add(rule);
66+
}
67+
}
68+
for (var rule in registeredRules) {
69+
if (!configuredRules.contains(rule)) {
70+
missingRules.add(rule);
71+
}
72+
}
73+
74+
if (extraRules.isEmpty && missingRules.isEmpty) {
75+
return null;
76+
}
77+
78+
var errors = StringBuffer();
79+
if (extraRules.isNotEmpty) {
80+
errors.writeln('Found unknown (or deprecated/removed) rules:');
81+
for (var rule in extraRules) {
82+
errors.writeln('- $rule');
83+
}
84+
}
85+
if (missingRules.isNotEmpty) {
86+
errors.writeln('Missing rules:');
87+
for (var rule in missingRules) {
88+
errors.writeln('- $rule');
89+
}
90+
}
91+
return errors.toString();
92+
}
93+
94+
/// Provides the options found in [optionsSource].
95+
Map<String, YamlNode> _getOptionsFromString(String optionsSource) {
96+
var options = <String, YamlNode>{};
97+
var doc = loadYamlNode(optionsSource);
98+
99+
// Empty options.
100+
if (doc is YamlScalar && doc.value == null) {
101+
return options;
102+
}
103+
if (doc is! YamlMap) {
104+
throw Exception(
105+
'Bad options file format (expected map, got ${doc.runtimeType})');
106+
}
107+
doc.nodes.forEach((k, YamlNode v) {
108+
if (k is! YamlScalar) {
109+
throw YamlException(
110+
'Bad options file format (expected YamlScalar key, got '
111+
"'${k.runtimeType}'",
112+
v.span,
113+
);
114+
}
115+
var key = k.value;
116+
if (key is! String) {
117+
throw YamlException(
118+
'Bad options file format (expected String key, got '
119+
"'${key.runtimeType})'",
120+
v.span,
121+
);
122+
}
123+
options[key] = v;
124+
});
125+
return options;
126+
}

pkg/linter/tool/machine.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import 'package:linter/src/rules.dart';
1313
import 'package:linter/src/utils.dart';
1414
import 'package:yaml/yaml.dart';
1515

16-
import '../test/test_constants.dart';
16+
import '../tool/util/path_utils.dart';
1717
import 'since.dart';
1818
import 'util/score_utils.dart' as score_utils;
1919

pkg/linter/tool/scorecard.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import 'package:linter/src/analyzer.dart';
1313
import 'package:linter/src/rules.dart';
1414
import 'package:linter/src/utils.dart';
1515

16-
import '../test/test_constants.dart';
16+
import '../tool/util/path_utils.dart';
1717
import 'crawl.dart';
1818
import 'parse.dart';
1919
import 'since.dart';

pkg/linter/tool/since.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'dart:io';
77

88
import 'package:yaml/yaml.dart';
99

10-
import '../test/test_constants.dart';
10+
import '../tool/util/path_utils.dart';
1111
import 'changelog.dart';
1212
import 'crawl.dart';
1313

pkg/linter/tool/util/path_utils.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:path/path.dart' as path;
8+
9+
List<String> get _packageRoot {
10+
var parts = path.split(path.dirname(path.fromUri(Platform.script.path)));
11+
while (parts.last != 'linter') {
12+
parts.removeLast();
13+
if (parts.isEmpty) {
14+
throw StateError("Script is not located inside a 'linter' directory? "
15+
"'${Platform.script.path}'");
16+
}
17+
}
18+
return parts;
19+
}
20+
21+
String pathRelativeToPackageRoot(Iterable<String> parts) =>
22+
path.joinAll([..._packageRoot, ...parts]);
23+
24+
String pathRelativeToPkgDir(Iterable<String> parts) {
25+
var pathParts = _packageRoot;
26+
pathParts.replaceRange(pathParts.length - 1, pathParts.length, parts);
27+
return path.joinAll(pathParts);
28+
}

0 commit comments

Comments
 (0)