Version 2.14.0-228.0.dev
Merge commit 'b929c663931d75c5b5c0520665133fd5d2f4470a' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index 88fbe28..fce6916 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -353,7 +353,19 @@
@override
OpType get opType {
- return _opType ??= OpType.forCompletion(target, offset);
+ var opType = _opType;
+ if (opType == null) {
+ opType = OpType.forCompletion(target, offset);
+ var contextType = this.contextType;
+ if (contextType is FunctionType) {
+ contextType = contextType.returnType;
+ }
+ if (contextType != null && contextType.isVoid) {
+ opType.includeVoidReturnSuggestions = true;
+ }
+ _opType = opType;
+ }
+ return opType;
}
/// The source range that represents the region of text that should be
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/add_missing_enum_like_case_clauses.dart b/pkg/analysis_server/lib/src/services/correction/dart/add_missing_enum_like_case_clauses.dart
new file mode 100644
index 0000000..b3e308f
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/add_missing_enum_like_case_clauses.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+
+class AddMissingEnumLikeCaseClauses extends CorrectionProducer {
+ @override
+ FixKind get fixKind => DartFixKind.ADD_MISSING_ENUM_CASE_CLAUSES;
+
+ // TODO: Consider enabling this lint for fix all in file.
+ // @override
+ // FixKind? get multiFixKind => super.multiFixKind;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ final node = this.node;
+ if (node is SwitchStatement) {
+ var expressionType = node.expression.staticType;
+ if (expressionType is! InterfaceType) {
+ return;
+ }
+ var classElement = expressionType.element;
+ var className = classElement.name;
+ var caseNames = _caseNames(node);
+ var missingNames = _constantNames(classElement)
+ ..removeWhere((e) => caseNames.contains(e));
+ missingNames.sort();
+ var firstCase = node.members[0];
+ var caseIndent = utils.getLinePrefix(firstCase.offset);
+ var statementIndent = utils.getLinePrefix(firstCase.statements[0].offset);
+ // TODO(brianwilkerson) Consider inserting the names in order into the
+ // switch statement.
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addInsertion(node.members.last.end, (builder) {
+ for (var name in missingNames) {
+ builder.writeln();
+ builder.write(caseIndent);
+ builder.write('case ');
+ builder.write(className);
+ builder.write('.');
+ builder.write(name);
+ builder.writeln(':');
+ builder.write(statementIndent);
+ builder.writeln('// TODO: Handle this case.');
+ builder.write(statementIndent);
+ builder.write('break;');
+ }
+ });
+ });
+ }
+ }
+
+ /// Return the names of the constants already in a case clause in the
+ /// [statement].
+ List<String> _caseNames(SwitchStatement statement) {
+ var caseNames = <String>[];
+ for (var member in statement.members) {
+ if (member is SwitchCase) {
+ var expression = member.expression;
+ if (expression is Identifier) {
+ var element = expression.staticElement;
+ if (element is PropertyAccessorElement) {
+ caseNames.add(element.name);
+ }
+ } else if (expression is PropertyAccess) {
+ caseNames.add(expression.propertyName.name);
+ }
+ }
+ }
+ return caseNames;
+ }
+
+ /// Return the names of the constants defined in [classElement].
+ List<String> _constantNames(ClassElement classElement) {
+ var type = classElement.thisType;
+ var constantNames = <String>[];
+ for (var field in classElement.fields) {
+ // Ensure static const.
+ if (field.isSynthetic || !field.isConst || !field.isStatic) {
+ continue;
+ }
+ // Check for type equality.
+ if (field.type != type) {
+ continue;
+ }
+ constantNames.add(field.name);
+ }
+ return constantNames;
+ }
+
+ /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+ static AddMissingEnumLikeCaseClauses newInstance() =>
+ AddMissingEnumLikeCaseClauses();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 78b321a..61a67e1 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -16,6 +16,7 @@
import 'package:analysis_server/src/services/correction/dart/add_field_formal_parameters.dart';
import 'package:analysis_server/src/services/correction/dart/add_late.dart';
import 'package:analysis_server/src/services/correction/dart/add_missing_enum_case_clauses.dart';
+import 'package:analysis_server/src/services/correction/dart/add_missing_enum_like_case_clauses.dart';
import 'package:analysis_server/src/services/correction/dart/add_missing_parameter.dart';
import 'package:analysis_server/src/services/correction/dart/add_missing_parameter_named.dart';
import 'package:analysis_server/src/services/correction/dart/add_missing_required_argument.dart';
@@ -395,6 +396,9 @@
RemoveEmptyStatement.newInstance,
ReplaceWithBrackets.newInstance,
],
+ LintNames.exhaustive_cases: [
+ AddMissingEnumLikeCaseClauses.newInstance,
+ ],
LintNames.hash_and_equals: [
CreateMethod.equalsOrHashCode,
],
diff --git a/pkg/analysis_server/lib/src/services/linter/lint_names.dart b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
index 2f20834..20e6839 100644
--- a/pkg/analysis_server/lib/src/services/linter/lint_names.dart
+++ b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
@@ -44,6 +44,7 @@
static const String empty_catches = 'empty_catches';
static const String empty_constructor_bodies = 'empty_constructor_bodies';
static const String empty_statements = 'empty_statements';
+ static const String exhaustive_cases = 'exhaustive_cases';
static const String hash_and_equals = 'hash_and_equals';
static const String no_duplicate_case_values = 'no_duplicate_case_values';
static const String non_constant_identifier_names =
diff --git a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
index f8171dc..59352d0 100644
--- a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
+++ b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
@@ -20,6 +20,7 @@
defineReflectiveTests(PropertyAccessCompletionTest);
defineReflectiveTests(RedirectedConstructorCompletionTest);
defineReflectiveTests(RedirectingConstructorInvocationCompletionTest);
+ defineReflectiveTests(ReturnStatementTest);
defineReflectiveTests(SuperConstructorInvocationCompletionTest);
defineReflectiveTests(VariableDeclarationListCompletionTest);
});
@@ -585,6 +586,48 @@
}
@reflectiveTest
+class ReturnStatementTest extends CompletionTestCase {
+ Future<void> test_voidFromVoid_localFunction() async {
+ addTestFile('''
+class C {
+ void m() {
+ void f() {
+ return ^
+ }
+ }
+ void g() {}
+}
+''');
+ await getSuggestions();
+ assertHasCompletion('g');
+ }
+
+ Future<void> test_voidFromVoid_method() async {
+ addTestFile('''
+class C {
+ void f() {
+ return ^
+ }
+ void g() {}
+}
+''');
+ await getSuggestions();
+ assertHasCompletion('g');
+ }
+
+ Future<void> test_voidFromVoid_topLevelFunction() async {
+ addTestFile('''
+void f() {
+ return ^
+}
+void g() {}
+''');
+ await getSuggestions();
+ assertHasCompletion('g');
+ }
+}
+
+@reflectiveTest
class SuperConstructorInvocationCompletionTest extends CompletionTestCase {
Future<void> test_namedConstructor_notVisible() async {
newFile('/project/bin/a.dart', content: '''
diff --git a/pkg/analysis_server/test/src/services/correction/fix/add_missing_enum_like_case_clauses_test.dart b/pkg/analysis_server/test/src/services/correction/fix/add_missing_enum_like_case_clauses_test.dart
new file mode 100644
index 0000000..6c299ca
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/add_missing_enum_like_case_clauses_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(AddMissingEnumLikeCaseClausesTest);
+ });
+}
+
+@reflectiveTest
+class AddMissingEnumLikeCaseClausesTest extends FixProcessorLintTest {
+ @override
+ FixKind get kind => DartFixKind.ADD_MISSING_ENUM_CASE_CLAUSES;
+
+ @override
+ String get lintCode => LintNames.exhaustive_cases;
+
+ Future<void> test_missing() async {
+ await resolveTestCode('''
+void f(E e) {
+ switch (e) {
+ case E.a:
+ break;
+ case E.b:
+ break;
+ }
+}
+class E {
+ static const E a = E._(0);
+ static const E b = E._(1);
+ static const E c = E._(2);
+ const E._(int x);
+}
+''');
+ await assertHasFix('''
+void f(E e) {
+ switch (e) {
+ case E.a:
+ break;
+ case E.b:
+ break;
+ case E.c:
+ // TODO: Handle this case.
+ break;
+ }
+}
+class E {
+ static const E a = E._(0);
+ static const E b = E._(1);
+ static const E c = E._(2);
+ const E._(int x);
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index f9946fa..ece1e25 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -15,6 +15,8 @@
import 'add_late_test.dart' as add_late;
import 'add_missing_enum_case_clauses_test.dart'
as add_missing_enum_case_clauses;
+import 'add_missing_enum_like_case_clauses_test.dart'
+ as add_missing_enum_like_case_clauses;
import 'add_missing_parameter_named_test.dart' as add_missing_parameter_named;
import 'add_missing_parameter_positional_test.dart'
as add_missing_parameter_positional;
@@ -193,6 +195,7 @@
add_field_formal_parameters.main();
add_late.main();
add_missing_enum_case_clauses.main();
+ add_missing_enum_like_case_clauses.main();
add_missing_parameter_named.main();
add_missing_parameter_positional.main();
add_missing_parameter_required.main();
diff --git a/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart b/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
index 077b56b..5bed787 100644
--- a/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
+++ b/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
@@ -6,14 +6,32 @@
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/pubspec/pubspec_warning_code.dart';
-import 'package:analyzer/src/util/file_paths.dart' as file_paths;
-import 'package:analyzer/src/util/yaml.dart';
-import 'package:path/path.dart' as path;
+import 'package:analyzer/src/pubspec/validators/dependency_validator.dart';
+import 'package:analyzer/src/pubspec/validators/flutter_validator.dart';
+import 'package:analyzer/src/pubspec/validators/name_validator.dart';
import 'package:source_span/src/span.dart';
import 'package:yaml/yaml.dart';
-class PubspecValidator {
+class BasePubspecValidator {
+ /// The resource provider used to access the file system.
+ final ResourceProvider provider;
+
+ /// The source representing the file being validated.
+ final Source source;
+
+ BasePubspecValidator(this.provider, this.source);
+
+ /// Report an error for the given node.
+ void reportErrorForNode(
+ ErrorReporter reporter, YamlNode node, ErrorCode errorCode,
+ [List<Object>? arguments]) {
+ SourceSpan span = node.span;
+ reporter.reportErrorForOffset(
+ errorCode, span.start.offset, span.length, arguments);
+ }
+}
+
+class PubspecField {
/// The name of the sub-field (under `flutter`) whose value is a list of
/// assets available to Flutter apps at runtime.
static const String ASSETS_FIELD = 'assets';
@@ -42,16 +60,25 @@
/// The name of the field whose value is the version of the package.
static const String VERSION_FIELD = 'version';
+}
+class PubspecValidator {
/// The resource provider used to access the file system.
final ResourceProvider provider;
/// The source representing the file being validated.
final Source source;
+ final DependencyValidator _dependencyValidator;
+ final FlutterValidator _flutterValidator;
+ final NameValidator _nameValidator;
+
/// Initialize a newly create validator to validate the content of the given
/// [source].
- PubspecValidator(this.provider, this.source);
+ PubspecValidator(this.provider, this.source)
+ : _dependencyValidator = DependencyValidator(provider, source),
+ _flutterValidator = FlutterValidator(provider, source),
+ _nameValidator = NameValidator(provider, source);
/// Validate the given [contents].
List<AnalysisError> validate(Map<dynamic, YamlNode> contents) {
@@ -62,226 +89,10 @@
isNonNullableByDefault: false,
);
- _validateDependencies(reporter, contents);
- _validateFlutter(reporter, contents);
- _validateName(reporter, contents);
+ _dependencyValidator.validate(reporter, contents);
+ _flutterValidator.validate(reporter, contents);
+ _nameValidator.validate(reporter, contents);
return recorder.errors;
}
-
- /// Return `true` if an asset (file) exists at the given absolute, normalized
- /// [assetPath] or in a subdirectory of the parent of the file.
- bool _assetExistsAtPath(String assetPath) {
- // Check for asset directories.
- Folder assetDirectory = provider.getFolder(assetPath);
- if (assetDirectory.exists) {
- return true;
- }
-
- // Else, check for an asset file.
- File assetFile = provider.getFile(assetPath);
- if (assetFile.exists) {
- return true;
- }
- String fileName = assetFile.shortName;
- Folder assetFolder = assetFile.parent2;
- if (!assetFolder.exists) {
- return false;
- }
- for (Resource child in assetFolder.getChildren()) {
- if (child is Folder) {
- File innerFile = child.getChildAssumingFile(fileName);
- if (innerFile.exists) {
- return true;
- }
- }
- }
- return false;
- }
-
- String? _asString(dynamic node) {
- if (node is String) {
- return node;
- }
- if (node is YamlScalar && node.value is String) {
- return node.value as String;
- }
- return null;
- }
-
- /// Return a map whose keys are the names of declared dependencies and whose
- /// values are the specifications of those dependencies. The map is extracted
- /// from the given [contents] using the given [key].
- Map<dynamic, YamlNode> _getDeclaredDependencies(
- ErrorReporter reporter, Map<dynamic, YamlNode> contents, String key) {
- var field = contents[key];
- if (field == null || (field is YamlScalar && field.value == null)) {
- return <String, YamlNode>{};
- } else if (field is YamlMap) {
- return field.nodes;
- }
- _reportErrorForNode(
- reporter, field, PubspecWarningCode.DEPENDENCIES_FIELD_NOT_MAP, [key]);
- return <String, YamlNode>{};
- }
-
- /// Report an error for the given node.
- void _reportErrorForNode(
- ErrorReporter reporter, YamlNode node, ErrorCode errorCode,
- [List<Object>? arguments]) {
- SourceSpan span = node.span;
- reporter.reportErrorForOffset(
- errorCode, span.start.offset, span.length, arguments);
- }
-
- /// Validate the value of the required `name` field.
- void _validateDependencies(
- ErrorReporter reporter, Map<dynamic, YamlNode> contents) {
- Map<dynamic, YamlNode> declaredDependencies =
- _getDeclaredDependencies(reporter, contents, DEPENDENCIES_FIELD);
- Map<dynamic, YamlNode> declaredDevDependencies =
- _getDeclaredDependencies(reporter, contents, DEV_DEPENDENCIES_FIELD);
-
- bool isPublishablePackage = false;
- var version = contents[VERSION_FIELD];
- if (version != null) {
- var publishTo = _asString(contents[PUBLISH_TO_FIELD]);
- if (publishTo != 'none') {
- isPublishablePackage = true;
- }
- }
-
- for (var dependency in declaredDependencies.entries) {
- _validatePathEntries(reporter, dependency.value, isPublishablePackage);
- }
-
- for (var dependency in declaredDevDependencies.entries) {
- var packageName = dependency.key as YamlNode;
- if (declaredDependencies.containsKey(packageName)) {
- _reportErrorForNode(reporter, packageName,
- PubspecWarningCode.UNNECESSARY_DEV_DEPENDENCY, [packageName.value]);
- }
- _validatePathEntries(reporter, dependency.value, false);
- }
- }
-
- /// Validate the value of the optional `flutter` field.
- void _validateFlutter(
- ErrorReporter reporter, Map<dynamic, YamlNode> contents) {
- var flutterField = contents[FLUTTER_FIELD];
- if (flutterField is YamlMap) {
- var assetsField = flutterField.nodes[ASSETS_FIELD];
- if (assetsField is YamlList) {
- path.Context context = provider.pathContext;
- String packageRoot = context.dirname(source.fullName);
- for (YamlNode entryValue in assetsField.nodes) {
- if (entryValue is YamlScalar) {
- Object entry = entryValue.value;
- if (entry is String) {
- if (entry.startsWith('packages/')) {
- // TODO(brianwilkerson) Add validation of package references.
- } else {
- bool isDirectoryEntry = entry.endsWith("/");
- String normalizedEntry =
- context.joinAll(path.posix.split(entry));
- String assetPath = context.join(packageRoot, normalizedEntry);
- if (!_assetExistsAtPath(assetPath)) {
- ErrorCode errorCode = isDirectoryEntry
- ? PubspecWarningCode.ASSET_DIRECTORY_DOES_NOT_EXIST
- : PubspecWarningCode.ASSET_DOES_NOT_EXIST;
- _reportErrorForNode(
- reporter, entryValue, errorCode, [entryValue.value]);
- }
- }
- } else {
- _reportErrorForNode(
- reporter, entryValue, PubspecWarningCode.ASSET_NOT_STRING);
- }
- } else {
- _reportErrorForNode(
- reporter, entryValue, PubspecWarningCode.ASSET_NOT_STRING);
- }
- }
- } else if (assetsField != null) {
- _reportErrorForNode(
- reporter, assetsField, PubspecWarningCode.ASSET_FIELD_NOT_LIST);
- }
-
- if (flutterField.length > 1) {
- // TODO(brianwilkerson) Should we report an error if `flutter` contains
- // keys other than `assets`?
- }
- } else if (flutterField != null) {
- if (flutterField.value == null) {
- // allow an empty `flutter:` section; explicitly fail on a non-empty,
- // non-map one
- } else {
- _reportErrorForNode(
- reporter, flutterField, PubspecWarningCode.FLUTTER_FIELD_NOT_MAP);
- }
- }
- }
-
- /// Validate the value of the required `name` field.
- void _validateName(ErrorReporter reporter, Map<dynamic, YamlNode> contents) {
- var nameField = contents[NAME_FIELD];
- if (nameField == null) {
- reporter.reportErrorForOffset(PubspecWarningCode.MISSING_NAME, 0, 0);
- } else if (nameField is! YamlScalar || nameField.value is! String) {
- _reportErrorForNode(
- reporter, nameField, PubspecWarningCode.NAME_NOT_STRING);
- }
- }
-
- /// Validate that `path` entries reference valid paths.
- ///
- /// Valid paths are directories that:
- ///
- /// 1. exist,
- /// 2. contain a pubspec.yaml file
- ///
- /// If [checkForPathAndGitDeps] is true, `git` or `path` dependencies will
- /// be marked invalid.
- void _validatePathEntries(ErrorReporter reporter, YamlNode dependency,
- bool checkForPathAndGitDeps) {
- if (dependency is YamlMap) {
- var pathEntry = _asString(dependency[PATH_FIELD]);
- if (pathEntry != null) {
- YamlNode pathKey() => dependency.getKey(PATH_FIELD)!;
- YamlNode pathValue() => dependency.valueAt(PATH_FIELD)!;
-
- if (pathEntry.contains(r'\')) {
- _reportErrorForNode(reporter, pathValue(),
- PubspecWarningCode.PATH_NOT_POSIX, [pathEntry]);
- return;
- }
- var context = provider.pathContext;
- var normalizedPath = context.joinAll(path.posix.split(pathEntry));
- var packageRoot = context.dirname(source.fullName);
- var dependencyPath = context.join(packageRoot, normalizedPath);
- dependencyPath = context.absolute(dependencyPath);
- dependencyPath = context.normalize(dependencyPath);
- var packageFolder = provider.getFolder(dependencyPath);
- if (!packageFolder.exists) {
- _reportErrorForNode(reporter, pathValue(),
- PubspecWarningCode.PATH_DOES_NOT_EXIST, [pathEntry]);
- } else {
- if (!packageFolder.getChild(file_paths.pubspecYaml).exists) {
- _reportErrorForNode(reporter, pathValue(),
- PubspecWarningCode.PATH_PUBSPEC_DOES_NOT_EXIST, [pathEntry]);
- }
- }
- if (checkForPathAndGitDeps) {
- _reportErrorForNode(reporter, pathKey(),
- PubspecWarningCode.INVALID_DEPENDENCY, [PATH_FIELD]);
- }
- }
-
- var gitEntry = dependency[GIT_FIELD];
- if (gitEntry != null && checkForPathAndGitDeps) {
- _reportErrorForNode(reporter, dependency.getKey(GIT_FIELD)!,
- PubspecWarningCode.INVALID_DEPENDENCY, [GIT_FIELD]);
- }
- }
- }
}
diff --git a/pkg/analyzer/lib/src/pubspec/validators/dependency_validator.dart b/pkg/analyzer/lib/src/pubspec/validators/dependency_validator.dart
new file mode 100644
index 0000000..49f859c
--- /dev/null
+++ b/pkg/analyzer/lib/src/pubspec/validators/dependency_validator.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// 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:analyzer/error/listener.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/pubspec/pubspec_validator.dart';
+import 'package:analyzer/src/pubspec/pubspec_warning_code.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
+import 'package:analyzer/src/util/yaml.dart';
+import 'package:path/path.dart' as path;
+import 'package:yaml/yaml.dart';
+
+class DependencyValidator extends BasePubspecValidator {
+ DependencyValidator(ResourceProvider provider, Source source)
+ : super(provider, source);
+
+ /// Validate the value of the required `name` field.
+ void validate(ErrorReporter reporter, Map<dynamic, YamlNode> contents) {
+ Map<dynamic, YamlNode> declaredDependencies = _getDeclaredDependencies(
+ reporter, contents, PubspecField.DEPENDENCIES_FIELD);
+ Map<dynamic, YamlNode> declaredDevDependencies = _getDeclaredDependencies(
+ reporter, contents, PubspecField.DEV_DEPENDENCIES_FIELD);
+
+ bool isPublishablePackage = false;
+ var version = contents[PubspecField.VERSION_FIELD];
+ if (version != null) {
+ var publishTo = _asString(contents[PubspecField.PUBLISH_TO_FIELD]);
+ if (publishTo != 'none') {
+ isPublishablePackage = true;
+ }
+ }
+
+ for (var dependency in declaredDependencies.entries) {
+ _validatePathEntries(reporter, dependency.value, isPublishablePackage);
+ }
+
+ for (var dependency in declaredDevDependencies.entries) {
+ var packageName = dependency.key as YamlNode;
+ if (declaredDependencies.containsKey(packageName)) {
+ reportErrorForNode(reporter, packageName,
+ PubspecWarningCode.UNNECESSARY_DEV_DEPENDENCY, [packageName.value]);
+ }
+ _validatePathEntries(reporter, dependency.value, false);
+ }
+ }
+
+ String? _asString(dynamic node) {
+ if (node is String) {
+ return node;
+ }
+ if (node is YamlScalar && node.value is String) {
+ return node.value as String;
+ }
+ return null;
+ }
+
+ /// Return a map whose keys are the names of declared dependencies and whose
+ /// values are the specifications of those dependencies. The map is extracted
+ /// from the given [contents] using the given [key].
+ Map<dynamic, YamlNode> _getDeclaredDependencies(
+ ErrorReporter reporter, Map<dynamic, YamlNode> contents, String key) {
+ var field = contents[key];
+ if (field == null || (field is YamlScalar && field.value == null)) {
+ return <String, YamlNode>{};
+ } else if (field is YamlMap) {
+ return field.nodes;
+ }
+ reportErrorForNode(
+ reporter, field, PubspecWarningCode.DEPENDENCIES_FIELD_NOT_MAP, [key]);
+ return <String, YamlNode>{};
+ }
+
+ /// Validate that `path` entries reference valid paths.
+ ///
+ /// Valid paths are directories that:
+ ///
+ /// 1. exist,
+ /// 2. contain a pubspec.yaml file
+ ///
+ /// If [checkForPathAndGitDeps] is true, `git` or `path` dependencies will
+ /// be marked invalid.
+ void _validatePathEntries(ErrorReporter reporter, YamlNode dependency,
+ bool checkForPathAndGitDeps) {
+ if (dependency is YamlMap) {
+ var pathEntry = _asString(dependency[PubspecField.PATH_FIELD]);
+ if (pathEntry != null) {
+ YamlNode pathKey() => dependency.getKey(PubspecField.PATH_FIELD)!;
+ YamlNode pathValue() => dependency.valueAt(PubspecField.PATH_FIELD)!;
+
+ if (pathEntry.contains(r'\')) {
+ reportErrorForNode(reporter, pathValue(),
+ PubspecWarningCode.PATH_NOT_POSIX, [pathEntry]);
+ return;
+ }
+ var context = provider.pathContext;
+ var normalizedPath = context.joinAll(path.posix.split(pathEntry));
+ var packageRoot = context.dirname(source.fullName);
+ var dependencyPath = context.join(packageRoot, normalizedPath);
+ dependencyPath = context.absolute(dependencyPath);
+ dependencyPath = context.normalize(dependencyPath);
+ var packageFolder = provider.getFolder(dependencyPath);
+ if (!packageFolder.exists) {
+ reportErrorForNode(reporter, pathValue(),
+ PubspecWarningCode.PATH_DOES_NOT_EXIST, [pathEntry]);
+ } else {
+ if (!packageFolder.getChild(file_paths.pubspecYaml).exists) {
+ reportErrorForNode(reporter, pathValue(),
+ PubspecWarningCode.PATH_PUBSPEC_DOES_NOT_EXIST, [pathEntry]);
+ }
+ }
+ if (checkForPathAndGitDeps) {
+ reportErrorForNode(reporter, pathKey(),
+ PubspecWarningCode.INVALID_DEPENDENCY, [PubspecField.PATH_FIELD]);
+ }
+ }
+
+ var gitEntry = dependency[PubspecField.GIT_FIELD];
+ if (gitEntry != null && checkForPathAndGitDeps) {
+ reportErrorForNode(reporter, dependency.getKey(PubspecField.GIT_FIELD)!,
+ PubspecWarningCode.INVALID_DEPENDENCY, [PubspecField.GIT_FIELD]);
+ }
+ }
+ }
+}
diff --git a/pkg/analyzer/lib/src/pubspec/validators/flutter_validator.dart b/pkg/analyzer/lib/src/pubspec/validators/flutter_validator.dart
new file mode 100644
index 0000000..bb8803b
--- /dev/null
+++ b/pkg/analyzer/lib/src/pubspec/validators/flutter_validator.dart
@@ -0,0 +1,103 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// 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:analyzer/error/error.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/pubspec/pubspec_validator.dart';
+import 'package:analyzer/src/pubspec/pubspec_warning_code.dart';
+import 'package:path/path.dart' as path;
+import 'package:yaml/yaml.dart';
+
+class FlutterValidator extends BasePubspecValidator {
+ FlutterValidator(ResourceProvider provider, Source source)
+ : super(provider, source);
+
+ /// Validate the value of the optional `flutter` field.
+ void validate(ErrorReporter reporter, Map<dynamic, YamlNode> contents) {
+ var flutterField = contents[PubspecField.FLUTTER_FIELD];
+ if (flutterField is YamlMap) {
+ var assetsField = flutterField.nodes[PubspecField.ASSETS_FIELD];
+ if (assetsField is YamlList) {
+ path.Context context = provider.pathContext;
+ String packageRoot = context.dirname(source.fullName);
+ for (YamlNode entryValue in assetsField.nodes) {
+ if (entryValue is YamlScalar) {
+ Object entry = entryValue.value;
+ if (entry is String) {
+ if (entry.startsWith('packages/')) {
+ // TODO(brianwilkerson) Add validation of package references.
+ } else {
+ bool isDirectoryEntry = entry.endsWith("/");
+ String normalizedEntry =
+ context.joinAll(path.posix.split(entry));
+ String assetPath = context.join(packageRoot, normalizedEntry);
+ if (!_assetExistsAtPath(assetPath)) {
+ ErrorCode errorCode = isDirectoryEntry
+ ? PubspecWarningCode.ASSET_DIRECTORY_DOES_NOT_EXIST
+ : PubspecWarningCode.ASSET_DOES_NOT_EXIST;
+ reportErrorForNode(
+ reporter, entryValue, errorCode, [entryValue.value]);
+ }
+ }
+ } else {
+ reportErrorForNode(
+ reporter, entryValue, PubspecWarningCode.ASSET_NOT_STRING);
+ }
+ } else {
+ reportErrorForNode(
+ reporter, entryValue, PubspecWarningCode.ASSET_NOT_STRING);
+ }
+ }
+ } else if (assetsField != null) {
+ reportErrorForNode(
+ reporter, assetsField, PubspecWarningCode.ASSET_FIELD_NOT_LIST);
+ }
+
+ if (flutterField.length > 1) {
+ // TODO(brianwilkerson) Should we report an error if `flutter` contains
+ // keys other than `assets`?
+ }
+ } else if (flutterField != null) {
+ if (flutterField.value == null) {
+ // allow an empty `flutter:` section; explicitly fail on a non-empty,
+ // non-map one
+ } else {
+ reportErrorForNode(
+ reporter, flutterField, PubspecWarningCode.FLUTTER_FIELD_NOT_MAP);
+ }
+ }
+ }
+
+ /// Return `true` if an asset (file) exists at the given absolute, normalized
+ /// [assetPath] or in a subdirectory of the parent of the file.
+ bool _assetExistsAtPath(String assetPath) {
+ // Check for asset directories.
+ Folder assetDirectory = provider.getFolder(assetPath);
+ if (assetDirectory.exists) {
+ return true;
+ }
+
+ // Else, check for an asset file.
+ File assetFile = provider.getFile(assetPath);
+ if (assetFile.exists) {
+ return true;
+ }
+ String fileName = assetFile.shortName;
+ Folder assetFolder = assetFile.parent2;
+ if (!assetFolder.exists) {
+ return false;
+ }
+ for (Resource child in assetFolder.getChildren()) {
+ if (child is Folder) {
+ File innerFile = child.getChildAssumingFile(fileName);
+ if (innerFile.exists) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/pkg/analyzer/lib/src/pubspec/validators/name_validator.dart b/pkg/analyzer/lib/src/pubspec/validators/name_validator.dart
new file mode 100644
index 0000000..9564ab7
--- /dev/null
+++ b/pkg/analyzer/lib/src/pubspec/validators/name_validator.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// 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:analyzer/error/listener.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/pubspec/pubspec_validator.dart';
+import 'package:analyzer/src/pubspec/pubspec_warning_code.dart';
+import 'package:yaml/yaml.dart';
+
+class NameValidator extends BasePubspecValidator {
+ NameValidator(ResourceProvider provider, Source source)
+ : super(provider, source);
+
+ /// Validate the value of the required `name` field.
+ void validate(ErrorReporter reporter, Map<dynamic, YamlNode> contents) {
+ var nameField = contents[PubspecField.NAME_FIELD];
+ if (nameField == null) {
+ reporter.reportErrorForOffset(PubspecWarningCode.MISSING_NAME, 0, 0);
+ } else if (nameField is! YamlScalar || nameField.value is! String) {
+ reportErrorForNode(
+ reporter, nameField, PubspecWarningCode.NAME_NOT_STRING);
+ }
+ }
+}
diff --git a/pkg/analyzer/test/src/pubspec/pubspec_test_support.dart b/pkg/analyzer/test/src/pubspec/pubspec_test_support.dart
new file mode 100644
index 0000000..e2816d5
--- /dev/null
+++ b/pkg/analyzer/test/src/pubspec/pubspec_test_support.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// 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:analyzer/error/error.dart';
+import 'package:analyzer/src/pubspec/pubspec_validator.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:meta/meta.dart';
+import 'package:yaml/yaml.dart';
+
+import '../../generated/test_support.dart';
+
+class BasePubspecValidatorTest with ResourceProviderMixin {
+ late PubspecValidator validator;
+
+ /// Assert that when the validator is used on the given [content] the
+ /// [expectedErrorCodes] are produced.
+ void assertErrors(String content, List<ErrorCode> expectedErrorCodes) {
+ YamlNode node = loadYamlNode(content);
+ if (node is! YamlMap) {
+ // The file is empty.
+ node = YamlMap();
+ }
+ List<AnalysisError> errors = validator.validate(node.nodes);
+ GatheringErrorListener listener = GatheringErrorListener();
+ listener.addAll(errors);
+ listener.assertErrorsWithCodes(expectedErrorCodes);
+ }
+
+ /// Assert that when the validator is used on the given [content] no errors
+ /// are produced.
+ void assertNoErrors(String content) {
+ assertErrors(content, []);
+ }
+
+ @mustCallSuper
+ void setUp() {
+ var pubspecFile = getFile('/sample/pubspec.yaml');
+ var source = pubspecFile.createSource();
+ validator = PubspecValidator(resourceProvider, source);
+ }
+}
diff --git a/pkg/analyzer/test/src/pubspec/pubspec_validator_test.dart b/pkg/analyzer/test/src/pubspec/pubspec_validator_test.dart
deleted file mode 100644
index 005d55f..0000000
--- a/pkg/analyzer/test/src/pubspec/pubspec_validator_test.dart
+++ /dev/null
@@ -1,544 +0,0 @@
-// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
-// 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:analyzer/error/error.dart';
-import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/pubspec/pubspec_validator.dart';
-import 'package:analyzer/src/pubspec/pubspec_warning_code.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
-import 'package:test_reflective_loader/test_reflective_loader.dart';
-import 'package:yaml/yaml.dart';
-
-import '../../generated/test_support.dart';
-
-main() {
- defineReflectiveSuite(() {
- defineReflectiveTests(PubspecValidatorTest);
- });
-}
-
-@reflectiveTest
-class PubspecValidatorTest with ResourceProviderMixin {
- late final PubspecValidator validator;
-
- /// Assert that when the validator is used on the given [content] the
- /// [expectedErrorCodes] are produced.
- void assertErrors(String content, List<ErrorCode> expectedErrorCodes) {
- YamlNode node = loadYamlNode(content);
- if (node is! YamlMap) {
- // The file is empty.
- node = YamlMap();
- }
- List<AnalysisError> errors = validator.validate(node.nodes);
- GatheringErrorListener listener = GatheringErrorListener();
- listener.addAll(errors);
- listener.assertErrorsWithCodes(expectedErrorCodes);
- }
-
- /// Assert that when the validator is used on the given [content] no errors
- /// are produced.
- void assertNoErrors(String content) {
- assertErrors(content, []);
- }
-
- void setUp() {
- File pubspecFile = getFile('/sample/pubspec.yaml');
- Source source = pubspecFile.createSource();
- validator = PubspecValidator(resourceProvider, source);
- }
-
- test_assetDirectoryDoesExist_noError() {
- newFolder('/sample/assets/logos');
- assertNoErrors('''
-name: sample
-flutter:
- assets:
- - assets/logos/
-''');
- }
-
- test_assetDirectoryDoesNotExist_error() {
- assertErrors('''
-name: sample
-flutter:
- assets:
- - assets/logos/
-''', [PubspecWarningCode.ASSET_DIRECTORY_DOES_NOT_EXIST]);
- }
-
- test_assetDoesNotExist_path_error() {
- assertErrors('''
-name: sample
-flutter:
- assets:
- - assets/my_icon.png
-''', [PubspecWarningCode.ASSET_DOES_NOT_EXIST]);
- }
-
- test_assetDoesNotExist_path_inRoot_noError() {
- newFile('/sample/assets/my_icon.png');
- assertNoErrors('''
-name: sample
-flutter:
- assets:
- - assets/my_icon.png
-''');
- }
-
- test_assetDoesNotExist_path_inSubdir_noError() {
- newFile('/sample/assets/images/2.0x/my_icon.png');
- assertNoErrors('''
-name: sample
-flutter:
- assets:
- - assets/images/my_icon.png
-''');
- }
-
- @failingTest
- test_assetDoesNotExist_uri_error() {
- assertErrors('''
-name: sample
-flutter:
- assets:
- - packages/icons/my_icon.png
-''', [PubspecWarningCode.ASSET_DOES_NOT_EXIST]);
- }
-
- test_assetDoesNotExist_uri_noError() {
- // TODO(brianwilkerson) Create a package named `icons` that contains the
- // referenced file, and a `.packages` file that references that package.
- assertNoErrors('''
-name: sample
-flutter:
- assets:
- - packages/icons/my_icon.png
-''');
- }
-
- test_assetFieldNotList_error_empty() {
- assertErrors('''
-name: sample
-flutter:
- assets:
-''', [PubspecWarningCode.ASSET_FIELD_NOT_LIST]);
- }
-
- test_assetFieldNotList_error_string() {
- assertErrors('''
-name: sample
-flutter:
- assets: assets/my_icon.png
-''', [PubspecWarningCode.ASSET_FIELD_NOT_LIST]);
- }
-
- test_assetFieldNotList_noError() {
- newFile('/sample/assets/my_icon.png');
- assertNoErrors('''
-name: sample
-flutter:
- assets:
- - assets/my_icon.png
-''');
- }
-
- test_assetNotString_error_int() {
- assertErrors('''
-name: sample
-flutter:
- assets:
- - 23
-''', [PubspecWarningCode.ASSET_NOT_STRING]);
- }
-
- test_assetNotString_error_map() {
- assertErrors('''
-name: sample
-flutter:
- assets:
- - my_icon:
- default: assets/my_icon.png
- large: assets/large/my_icon.png
-''', [PubspecWarningCode.ASSET_NOT_STRING]);
- }
-
- test_assetNotString_noError() {
- newFile('/sample/assets/my_icon.png');
- assertNoErrors('''
-name: sample
-flutter:
- assets:
- - assets/my_icon.png
-''');
- }
-
- test_dependenciesField_empty() {
- assertNoErrors('''
-name: sample
-dependencies:
-''');
- }
-
- test_dependenciesFieldNotMap_error_bool() {
- assertErrors('''
-name: sample
-dependencies: true
-''', [PubspecWarningCode.DEPENDENCIES_FIELD_NOT_MAP]);
- }
-
- test_dependenciesFieldNotMap_noError() {
- assertNoErrors('''
-name: sample
-dependencies:
- a: any
-''');
- }
-
- test_dependencyGit_malformed_empty() {
- // todo (pq): consider validating.
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- git:
-''');
- }
-
- test_dependencyGit_malformed_list() {
- // todo (pq): consider validating.
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- git:
- - baz
-''');
- }
-
- test_dependencyGit_malformed_scalar() {
- // todo (pq): consider validating.
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- git: baz
-''');
- }
-
- test_dependencyGit_noVersion_valid() {
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- git:
- url: git@github.com:foo/foo.git
- path: path/to/foo
-''');
- }
-
- test_dependencyGit_version_error() {
- assertErrors('''
-name: sample
-version: 0.1.0
-dependencies:
- foo:
- git:
- url: git@github.com:foo/foo.git
- path: path/to/foo
-''', [PubspecWarningCode.INVALID_DEPENDENCY]);
- }
-
- test_dependencyGit_version_valid() {
- assertNoErrors('''
-name: sample
-version: 0.1.0
-publish_to: none
-dependencies:
- foo:
- git:
- url: git@github.com:foo/foo.git
- path: path/to/foo
-''');
- }
-
- test_dependencyGitPath() {
- // git paths are not validated
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- git:
- url: git@github.com:foo/foo.git
- path: path/to/foo
-''');
- }
-
- test_dependencyPath_malformed_empty() {
- // todo (pq): consider validating.
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- path:
-''');
- }
-
- test_dependencyPath_malformed_list() {
- // todo (pq): consider validating.
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- path:
- - baz
-''');
- }
-
- test_dependencyPath_noVersion_valid() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- path: /foo
-''');
- }
-
- test_dependencyPath_pubspecDoesNotExist() {
- newFolder('/foo');
- assertErrors('''
-name: sample
-dependencies:
- foo:
- path: /foo
-''', [PubspecWarningCode.PATH_PUBSPEC_DOES_NOT_EXIST]);
- }
-
- test_dependencyPath_pubspecExists() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- path: /foo
-''');
- }
-
- test_dependencyPath_valid_absolute() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- path: /foo
-''');
- }
-
- test_dependencyPath_valid_relative() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertNoErrors('''
-name: sample
-dependencies:
- foo:
- path: ../foo
-''');
- }
-
- test_dependencyPath_version_error() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertErrors('''
-name: sample
-version: 0.1.0
-dependencies:
- foo:
- path: /foo
-''', [PubspecWarningCode.INVALID_DEPENDENCY]);
- }
-
- test_dependencyPath_version_valid() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertNoErrors('''
-name: sample
-version: 0.1.0
-publish_to: none
-dependencies:
- foo:
- path: /foo
-''');
- }
-
- test_dependencyPathDoesNotExist_path_error() {
- assertErrors('''
-name: sample
-dependencies:
- foo:
- path: does/not/exist
-''', [PubspecWarningCode.PATH_DOES_NOT_EXIST]);
- }
-
- test_devDependenciesField_empty() {
- assertNoErrors('''
-name: sample
-dev_dependencies:
-''');
- }
-
- test_devDependenciesFieldNotMap_dev_error_bool() {
- assertErrors('''
-name: sample
-dev_dependencies: true
-''', [PubspecWarningCode.DEPENDENCIES_FIELD_NOT_MAP]);
- }
-
- test_devDependenciesFieldNotMap_dev_noError() {
- assertNoErrors('''
-name: sample
-dev_dependencies:
- a: any
-''');
- }
-
- test_devDependencyGit_version_no_error() {
- // Git paths are OK in dev_dependencies
- assertNoErrors('''
-name: sample
-version: 0.1.0
-dev_dependencies:
- foo:
- git:
- url: git@github.com:foo/foo.git
- path: path/to/foo
-''');
- }
-
- test_devDependencyPathDoesNotExist_path_error() {
- assertErrors('''
-name: sample
-dev_dependencies:
- foo:
- path: does/not/exist
-''', [PubspecWarningCode.PATH_DOES_NOT_EXIST]);
- }
-
- test_devDependencyPathExists() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertNoErrors('''
-name: sample
-dev_dependencies:
- foo:
- path: /foo
-''');
- }
-
- test_flutterField_empty_noError() {
- assertNoErrors('''
-name: sample
-flutter:
-''');
-
- assertNoErrors('''
-name: sample
-flutter:
-
-''');
- }
-
- test_flutterFieldNotMap_error_bool() {
- assertErrors('''
-name: sample
-flutter: true
-''', [PubspecWarningCode.FLUTTER_FIELD_NOT_MAP]);
- }
-
- test_flutterFieldNotMap_noError() {
- newFile('/sample/assets/my_icon.png');
- assertNoErrors('''
-name: sample
-flutter:
- assets:
- - assets/my_icon.png
-''');
- }
-
- test_missingName_error() {
- assertErrors('', [PubspecWarningCode.MISSING_NAME]);
- }
-
- test_missingName_noError() {
- assertNoErrors('''
-name: sample
-''');
- }
-
- test_nameNotString_error_int() {
- assertErrors('''
-name: 42
-''', [PubspecWarningCode.NAME_NOT_STRING]);
- }
-
- test_nameNotString_noError() {
- assertNoErrors('''
-name: sample
-''');
- }
-
- test_pathNotPosix_error() {
- newFolder('/foo');
- newPubspecYamlFile('/foo', '''
-name: foo
-''');
- assertErrors(r'''
-name: sample
-version: 0.1.0
-publish_to: none
-dependencies:
- foo:
- path: \foo
-''', [
- PubspecWarningCode.PATH_NOT_POSIX,
- ]);
- }
-
- test_unnecessaryDevDependency_error() {
- assertErrors('''
-name: sample
-dependencies:
- a: any
-dev_dependencies:
- a: any
-''', [PubspecWarningCode.UNNECESSARY_DEV_DEPENDENCY]);
- }
-
- test_unnecessaryDevDependency_noError() {
- assertNoErrors('''
-name: sample
-dependencies:
- a: any
-dev_dependencies:
- b: any
-''');
- }
-}
diff --git a/pkg/analyzer/test/src/pubspec/test_all.dart b/pkg/analyzer/test/src/pubspec/test_all.dart
index c4aa6ea..d5ef1de 100644
--- a/pkg/analyzer/test/src/pubspec/test_all.dart
+++ b/pkg/analyzer/test/src/pubspec/test_all.dart
@@ -4,10 +4,10 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
-import 'pubspec_validator_test.dart' as pubspec_validator;
+import 'validators/test_all.dart' as validator_tests;
main() {
defineReflectiveSuite(() {
- pubspec_validator.main();
+ validator_tests.main();
}, name: 'pubspec');
}
diff --git a/pkg/analyzer/test/src/pubspec/validators/pubspec_dependency_validator_test.dart b/pkg/analyzer/test/src/pubspec/validators/pubspec_dependency_validator_test.dart
new file mode 100644
index 0000000..cb52a95
--- /dev/null
+++ b/pkg/analyzer/test/src/pubspec/validators/pubspec_dependency_validator_test.dart
@@ -0,0 +1,333 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// 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:analyzer/src/pubspec/pubspec_warning_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../pubspec_test_support.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(PubspecDependencyValidatorTest);
+ });
+}
+
+@reflectiveTest
+class PubspecDependencyValidatorTest extends BasePubspecValidatorTest {
+ test_dependenciesField_empty() {
+ assertNoErrors('''
+name: sample
+dependencies:
+''');
+ }
+
+ test_dependenciesFieldNotMap_error_bool() {
+ assertErrors('''
+name: sample
+dependencies: true
+''', [PubspecWarningCode.DEPENDENCIES_FIELD_NOT_MAP]);
+ }
+
+ test_dependenciesFieldNotMap_noError() {
+ assertNoErrors('''
+name: sample
+dependencies:
+ a: any
+''');
+ }
+
+ test_dependencyGit_malformed_empty() {
+ // todo (pq): consider validating.
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ git:
+''');
+ }
+
+ test_dependencyGit_malformed_list() {
+ // todo (pq): consider validating.
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ git:
+ - baz
+''');
+ }
+
+ test_dependencyGit_malformed_scalar() {
+ // todo (pq): consider validating.
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ git: baz
+''');
+ }
+
+ test_dependencyGit_noVersion_valid() {
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ git:
+ url: git@github.com:foo/foo.git
+ path: path/to/foo
+''');
+ }
+
+ test_dependencyGit_version_error() {
+ assertErrors('''
+name: sample
+version: 0.1.0
+dependencies:
+ foo:
+ git:
+ url: git@github.com:foo/foo.git
+ path: path/to/foo
+''', [PubspecWarningCode.INVALID_DEPENDENCY]);
+ }
+
+ test_dependencyGit_version_valid() {
+ assertNoErrors('''
+name: sample
+version: 0.1.0
+publish_to: none
+dependencies:
+ foo:
+ git:
+ url: git@github.com:foo/foo.git
+ path: path/to/foo
+''');
+ }
+
+ test_dependencyGitPath() {
+ // git paths are not validated
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ git:
+ url: git@github.com:foo/foo.git
+ path: path/to/foo
+''');
+ }
+
+ test_dependencyPath_malformed_empty() {
+ // todo (pq): consider validating.
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ path:
+''');
+ }
+
+ test_dependencyPath_malformed_list() {
+ // todo (pq): consider validating.
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ path:
+ - baz
+''');
+ }
+
+ test_dependencyPath_noVersion_valid() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ path: /foo
+''');
+ }
+
+ test_dependencyPath_pubspecDoesNotExist() {
+ newFolder('/foo');
+ assertErrors('''
+name: sample
+dependencies:
+ foo:
+ path: /foo
+''', [PubspecWarningCode.PATH_PUBSPEC_DOES_NOT_EXIST]);
+ }
+
+ test_dependencyPath_pubspecExists() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ path: /foo
+''');
+ }
+
+ test_dependencyPath_valid_absolute() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ path: /foo
+''');
+ }
+
+ test_dependencyPath_valid_relative() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertNoErrors('''
+name: sample
+dependencies:
+ foo:
+ path: ../foo
+''');
+ }
+
+ test_dependencyPath_version_error() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertErrors('''
+name: sample
+version: 0.1.0
+dependencies:
+ foo:
+ path: /foo
+''', [PubspecWarningCode.INVALID_DEPENDENCY]);
+ }
+
+ test_dependencyPath_version_valid() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertNoErrors('''
+name: sample
+version: 0.1.0
+publish_to: none
+dependencies:
+ foo:
+ path: /foo
+''');
+ }
+
+ test_dependencyPathDoesNotExist_path_error() {
+ assertErrors('''
+name: sample
+dependencies:
+ foo:
+ path: does/not/exist
+''', [PubspecWarningCode.PATH_DOES_NOT_EXIST]);
+ }
+
+ test_devDependenciesField_empty() {
+ assertNoErrors('''
+name: sample
+dev_dependencies:
+''');
+ }
+
+ test_devDependenciesFieldNotMap_dev_error_bool() {
+ assertErrors('''
+name: sample
+dev_dependencies: true
+''', [PubspecWarningCode.DEPENDENCIES_FIELD_NOT_MAP]);
+ }
+
+ test_devDependenciesFieldNotMap_dev_noError() {
+ assertNoErrors('''
+name: sample
+dev_dependencies:
+ a: any
+''');
+ }
+
+ test_devDependencyGit_version_no_error() {
+ // Git paths are OK in dev_dependencies
+ assertNoErrors('''
+name: sample
+version: 0.1.0
+dev_dependencies:
+ foo:
+ git:
+ url: git@github.com:foo/foo.git
+ path: path/to/foo
+''');
+ }
+
+ test_devDependencyPathDoesNotExist_path_error() {
+ assertErrors('''
+name: sample
+dev_dependencies:
+ foo:
+ path: does/not/exist
+''', [PubspecWarningCode.PATH_DOES_NOT_EXIST]);
+ }
+
+ test_devDependencyPathExists() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertNoErrors('''
+name: sample
+dev_dependencies:
+ foo:
+ path: /foo
+''');
+ }
+
+ test_pathNotPosix_error() {
+ newFolder('/foo');
+ newPubspecYamlFile('/foo', '''
+name: foo
+''');
+ assertErrors(r'''
+name: sample
+version: 0.1.0
+publish_to: none
+dependencies:
+ foo:
+ path: \foo
+''', [
+ PubspecWarningCode.PATH_NOT_POSIX,
+ ]);
+ }
+
+ test_unnecessaryDevDependency_error() {
+ assertErrors('''
+name: sample
+dependencies:
+ a: any
+dev_dependencies:
+ a: any
+''', [PubspecWarningCode.UNNECESSARY_DEV_DEPENDENCY]);
+ }
+
+ test_unnecessaryDevDependency_noError() {
+ assertNoErrors('''
+name: sample
+dependencies:
+ a: any
+dev_dependencies:
+ b: any
+''');
+ }
+}
diff --git a/pkg/analyzer/test/src/pubspec/validators/pubspec_flutter_validator_test.dart b/pkg/analyzer/test/src/pubspec/validators/pubspec_flutter_validator_test.dart
new file mode 100644
index 0000000..34c9489
--- /dev/null
+++ b/pkg/analyzer/test/src/pubspec/validators/pubspec_flutter_validator_test.dart
@@ -0,0 +1,172 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// 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:analyzer/src/pubspec/pubspec_warning_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../pubspec_test_support.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(PubspecFlutterValidatorTest);
+ });
+}
+
+@reflectiveTest
+class PubspecFlutterValidatorTest extends BasePubspecValidatorTest {
+ test_assetDirectoryDoesExist_noError() {
+ newFolder('/sample/assets/logos');
+ assertNoErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/logos/
+''');
+ }
+
+ test_assetDirectoryDoesNotExist_error() {
+ assertErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/logos/
+''', [PubspecWarningCode.ASSET_DIRECTORY_DOES_NOT_EXIST]);
+ }
+
+ test_assetDoesNotExist_path_error() {
+ assertErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/my_icon.png
+''', [PubspecWarningCode.ASSET_DOES_NOT_EXIST]);
+ }
+
+ test_assetDoesNotExist_path_inRoot_noError() {
+ newFile('/sample/assets/my_icon.png');
+ assertNoErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/my_icon.png
+''');
+ }
+
+ test_assetDoesNotExist_path_inSubdir_noError() {
+ newFile('/sample/assets/images/2.0x/my_icon.png');
+ assertNoErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/images/my_icon.png
+''');
+ }
+
+ @failingTest
+ test_assetDoesNotExist_uri_error() {
+ assertErrors('''
+name: sample
+flutter:
+ assets:
+ - packages/icons/my_icon.png
+''', [PubspecWarningCode.ASSET_DOES_NOT_EXIST]);
+ }
+
+ test_assetDoesNotExist_uri_noError() {
+ // TODO(brianwilkerson) Create a package named `icons` that contains the
+ // referenced file, and a `.packages` file that references that package.
+ assertNoErrors('''
+name: sample
+flutter:
+ assets:
+ - packages/icons/my_icon.png
+''');
+ }
+
+ test_assetFieldNotList_error_empty() {
+ assertErrors('''
+name: sample
+flutter:
+ assets:
+''', [PubspecWarningCode.ASSET_FIELD_NOT_LIST]);
+ }
+
+ test_assetFieldNotList_error_string() {
+ assertErrors('''
+name: sample
+flutter:
+ assets: assets/my_icon.png
+''', [PubspecWarningCode.ASSET_FIELD_NOT_LIST]);
+ }
+
+ test_assetFieldNotList_noError() {
+ newFile('/sample/assets/my_icon.png');
+ assertNoErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/my_icon.png
+''');
+ }
+
+ test_assetNotString_error_int() {
+ assertErrors('''
+name: sample
+flutter:
+ assets:
+ - 23
+''', [PubspecWarningCode.ASSET_NOT_STRING]);
+ }
+
+ test_assetNotString_error_map() {
+ assertErrors('''
+name: sample
+flutter:
+ assets:
+ - my_icon:
+ default: assets/my_icon.png
+ large: assets/large/my_icon.png
+''', [PubspecWarningCode.ASSET_NOT_STRING]);
+ }
+
+ test_assetNotString_noError() {
+ newFile('/sample/assets/my_icon.png');
+ assertNoErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/my_icon.png
+''');
+ }
+
+ test_flutterField_empty_noError() {
+ assertNoErrors('''
+name: sample
+flutter:
+''');
+
+ assertNoErrors('''
+name: sample
+flutter:
+
+''');
+ }
+
+ test_flutterFieldNotMap_error_bool() {
+ assertErrors('''
+name: sample
+flutter: true
+''', [PubspecWarningCode.FLUTTER_FIELD_NOT_MAP]);
+ }
+
+ test_flutterFieldNotMap_noError() {
+ newFile('/sample/assets/my_icon.png');
+ assertNoErrors('''
+name: sample
+flutter:
+ assets:
+ - assets/my_icon.png
+''');
+ }
+}
diff --git a/pkg/analyzer/test/src/pubspec/validators/pubspec_name_validator_test.dart b/pkg/analyzer/test/src/pubspec/validators/pubspec_name_validator_test.dart
new file mode 100644
index 0000000..71a7b97
--- /dev/null
+++ b/pkg/analyzer/test/src/pubspec/validators/pubspec_name_validator_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// 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:analyzer/src/pubspec/pubspec_warning_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../pubspec_test_support.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(PubspecNameValidatorTest);
+ });
+}
+
+@reflectiveTest
+class PubspecNameValidatorTest extends BasePubspecValidatorTest {
+ test_missingName_error() {
+ assertErrors('', [PubspecWarningCode.MISSING_NAME]);
+ }
+
+ test_missingName_noError() {
+ assertNoErrors('''
+name: sample
+''');
+ }
+
+ test_nameNotString_error_int() {
+ assertErrors('''
+name: 42
+''', [PubspecWarningCode.NAME_NOT_STRING]);
+ }
+
+ test_nameNotString_noError() {
+ assertNoErrors('''
+name: sample
+''');
+ }
+}
diff --git a/pkg/analyzer/test/src/pubspec/validators/test_all.dart b/pkg/analyzer/test/src/pubspec/validators/test_all.dart
new file mode 100644
index 0000000..cd7a55c
--- /dev/null
+++ b/pkg/analyzer/test/src/pubspec/validators/test_all.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'pubspec_dependency_validator_test.dart'
+ as pubspec_dependency_validator_test;
+import 'pubspec_flutter_validator_test.dart' as pubspec_flutter_validator_test;
+import 'pubspec_name_validator_test.dart' as pubspec_name_validator_test;
+
+main() {
+ defineReflectiveSuite(() {
+ pubspec_dependency_validator_test.main();
+ pubspec_flutter_validator_test.main();
+ pubspec_name_validator_test.main();
+ }, name: 'validators');
+}
diff --git a/tools/VERSION b/tools/VERSION
index 6b36631..bcc1746 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 227
+PRERELEASE 228
PRERELEASE_PATCH 0
\ No newline at end of file