mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 01:10:27 +00:00
modularize pubspec validation
Some refactoring in preparation for adding a bunch of additional pubspec validation to analyzer. Change-Id: Iacda7fd098aeab01d3ed2bafc314f018a2eb6504 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/204340 Commit-Queue: Phil Quitslund <pquitslund@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
9da9768992
commit
b929c66393
|
@ -6,14 +6,32 @@ 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_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 @@ class PubspecValidator {
|
|||
|
||||
/// 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 @@ class PubspecValidator {
|
|||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
103
pkg/analyzer/lib/src/pubspec/validators/flutter_validator.dart
Normal file
103
pkg/analyzer/lib/src/pubspec/validators/flutter_validator.dart
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
26
pkg/analyzer/lib/src/pubspec/validators/name_validator.dart
Normal file
26
pkg/analyzer/lib/src/pubspec/validators/name_validator.dart
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
42
pkg/analyzer/test/src/pubspec/pubspec_test_support.dart
Normal file
42
pkg/analyzer/test/src/pubspec/pubspec_test_support.dart
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -2,178 +2,19 @@
|
|||
// 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';
|
||||
import '../pubspec_test_support.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(PubspecValidatorTest);
|
||||
defineReflectiveTests(PubspecDependencyValidatorTest);
|
||||
});
|
||||
}
|
||||
|
||||
@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
|
||||
''');
|
||||
}
|
||||
|
||||
class PubspecDependencyValidatorTest extends BasePubspecValidatorTest {
|
||||
test_dependenciesField_empty() {
|
||||
assertNoErrors('''
|
||||
name: sample
|
||||
|
@ -453,58 +294,6 @@ dev_dependencies:
|
|||
''');
|
||||
}
|
||||
|
||||
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', '''
|
|
@ -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
|
||||
''');
|
||||
}
|
||||
}
|
|
@ -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
|
||||
''');
|
||||
}
|
||||
}
|
18
pkg/analyzer/test/src/pubspec/validators/test_all.dart
Normal file
18
pkg/analyzer/test/src/pubspec/validators/test_all.dart
Normal file
|
@ -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');
|
||||
}
|
Loading…
Reference in a new issue