Warn about publishing in mixed mode (#2583)
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index acb9691..f04c11f 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -96,7 +96,7 @@
final upgradablePubspec = includeDevDependencies
? rootPubspec
- : _stripDevDependencies(rootPubspec);
+ : stripDevDependencies(rootPubspec);
final resolvablePubspec = _stripVersionConstraints(upgradablePubspec);
@@ -104,8 +104,8 @@
List<PackageId> resolvablePackages;
await log.spinner('Resolving', () async {
- upgradablePackages = await _tryResolve(upgradablePubspec);
- resolvablePackages = await _tryResolve(resolvablePubspec);
+ upgradablePackages = await _tryResolve(upgradablePubspec, cache);
+ resolvablePackages = await _tryResolve(resolvablePubspec, cache);
}, condition: _shouldShowSpinner);
// This list will be empty if there is no lock file.
@@ -313,23 +313,23 @@
return nonDevDependencies;
}
+}
- /// Try to solve [pubspec] return [PackageId]'s in the resolution or `null`.
- Future<List<PackageId>> _tryResolve(Pubspec pubspec) async {
- try {
- return (await resolveVersions(
- SolveType.UPGRADE,
- cache,
- Package.inMemory(pubspec),
- ))
- .packages;
- } on SolveFailure {
- return [];
- }
+/// Try to solve [pubspec] return [PackageId]'s in the resolution or `null`.
+Future<List<PackageId>> _tryResolve(Pubspec pubspec, SystemCache cache) async {
+ try {
+ return (await resolveVersions(
+ SolveType.UPGRADE,
+ cache,
+ Package.inMemory(pubspec),
+ ))
+ .packages;
+ } on SolveFailure {
+ return [];
}
}
-Pubspec _stripDevDependencies(Pubspec original) {
+Pubspec stripDevDependencies(Pubspec original) {
return Pubspec(
original.name,
version: original.version,
@@ -685,7 +685,7 @@
if (versionDetails != null) {
final nullSafety = nullSafetyMap[versionDetails._id];
- switch (nullSafety) {
+ switch (nullSafety.compliance) {
case NullSafetyCompliance.analysisFailed:
color = color = log.gray;
prefix = '?';
@@ -698,7 +698,7 @@
asDesired = true;
break;
case NullSafetyCompliance.notCompliant:
- case NullSafetyCompliance.apiOnly:
+ case NullSafetyCompliance.mixed:
color = log.red;
prefix = '✗';
nullSafetyJson = false;
diff --git a/lib/src/language_version.dart b/lib/src/language_version.dart
new file mode 100644
index 0000000..1c99bb3
--- /dev/null
+++ b/lib/src/language_version.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2020, 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/dart/ast/token.dart';
+import 'package:pub_semver/pub_semver.dart';
+
+/// A Dart language version as defined by
+/// https://github.com/dart-lang/language/blob/master/accepted/future-releases/language-versioning/feature-specification.md
+class LanguageVersion implements Comparable<LanguageVersion> {
+ final int major;
+ final int minor;
+
+ const LanguageVersion(this.major, this.minor);
+
+ /// The language version implied by a Dart sdk version.
+ factory LanguageVersion.fromVersion(Version version) =>
+ LanguageVersion(version.major, version.minor);
+
+ /// The language version implied by a Dart sdk version range.
+ ///
+ /// Throws if the versionRange has no lower bound.
+ factory LanguageVersion.fromVersionRange(VersionRange range) {
+ final min = range.min;
+ if (min == null) {
+ // TODO(sigurdm): is this right?
+ throw ArgumentError(
+ 'Version range with no lower bound does not imply a language version');
+ }
+ return LanguageVersion(min.major, min.minor);
+ }
+
+ /// The language version implied by a Dart sdk version.
+ factory LanguageVersion.fromLanguageVersionToken(
+ LanguageVersionToken version) =>
+ LanguageVersion(version.major, version.minor);
+
+ bool get supportsNullSafety => this >= firstVersionWithNullSafety;
+
+ @override
+ int compareTo(LanguageVersion other) {
+ if (major != other.major) return major.compareTo(other.major);
+ return minor.compareTo(other.minor);
+ }
+
+ bool operator <(LanguageVersion other) => compareTo(other) < 0;
+ bool operator >(LanguageVersion other) => compareTo(other) > 0;
+ bool operator <=(LanguageVersion other) => compareTo(other) <= 0;
+ bool operator >=(LanguageVersion other) => compareTo(other) >= 0;
+
+ static const firstVersionWithNullSafety = LanguageVersion(2, 10);
+
+ @override
+ String toString() => '$major.$minor';
+}
diff --git a/lib/src/null_safety_analysis.dart b/lib/src/null_safety_analysis.dart
index 9939aa5..976ab18 100644
--- a/lib/src/null_safety_analysis.dart
+++ b/lib/src/null_safety_analysis.dart
@@ -6,12 +6,15 @@
import 'package:analyzer/dart/analysis/context_builder.dart';
import 'package:analyzer/dart/analysis/context_locator.dart';
+import 'package:analyzer/dart/ast/token.dart';
import 'package:cli_util/cli_util.dart';
+import 'package:source_span/source_span.dart';
-import 'package:pub_semver/pub_semver.dart';
import 'package:path/path.dart' as path;
+import 'package:yaml/yaml.dart';
import 'io.dart';
+import 'language_version.dart';
import 'package.dart';
import 'package_name.dart';
import 'pubspec.dart';
@@ -26,7 +29,7 @@
/// This package opted into null safety, but some file or dependency is not
/// opted in.
- apiOnly,
+ mixed,
/// This package did not opt-in to null safety yet.
notCompliant,
@@ -48,9 +51,8 @@
/// Furthermore by awaiting the Future stored here, we avoid race-conditions
/// from downloading the same package-version into [_systemCache]
/// simultaneously when doing concurrent analyses.
- final Map<PackageId, Future<NullSafetyCompliance>>
+ final Map<PackageId, Future<NullSafetyAnalysisResult>>
_packageInternallyGoodCache = {};
- static final _firstVersionWithNullSafety = Version.parse('2.10.0');
NullSafetyAnalysis(SystemCache systemCache) : _systemCache = systemCache;
@@ -67,7 +69,7 @@
///
/// If [packageId] is a relative path dependency [containingPath] must be
/// provided with an absolute path to resolve it against.
- Future<NullSafetyCompliance> nullSafetyCompliance(PackageId packageId,
+ Future<NullSafetyAnalysisResult> nullSafetyCompliance(PackageId packageId,
{String containingPath}) async {
// A space in the name prevents clashes with other package names.
final rootName = '${packageId.name} importer';
@@ -86,6 +88,21 @@
},
sources: _systemCache.sources));
+ final rootPubspec =
+ await packageId.source.bind(_systemCache).describe(packageId);
+ final rootLanguageVersion = rootPubspec.languageVersion;
+ if (!rootLanguageVersion.supportsNullSafety) {
+ final span =
+ _tryGetSpanFromYamlMap(rootPubspec.fields['environment'], 'sdk');
+ final where = span == null
+ ? 'in the sdk constraint in the enviroment key in pubspec.yaml.'
+ : 'in pubspec.yaml: \n${span.highlight()}';
+ return NullSafetyAnalysisResult(
+ NullSafetyCompliance.notCompliant,
+ 'Is not opting in to null safety $where',
+ );
+ }
+
SolveResult result;
try {
result = await resolveVersions(
@@ -93,22 +110,30 @@
_systemCache,
root,
);
- } on SolveFailure {
- return NullSafetyCompliance.analysisFailed;
+ } on SolveFailure catch (e) {
+ return NullSafetyAnalysisResult(NullSafetyCompliance.analysisFailed,
+ 'Could not resolve constraints: $e');
}
- var allPackagesGood = true;
+ NullSafetyAnalysisResult firstBadPackage;
for (final dependencyId in result.packages) {
if (dependencyId.name == root.name) continue;
- final packageInternallyGood =
+ final packageInternalAnalysis =
await _packageInternallyGoodCache.putIfAbsent(dependencyId, () async {
final boundSource = dependencyId.source.bind(_systemCache);
final pubspec = await boundSource.describe(dependencyId);
- final languageVersion = _languageVersion(pubspec);
- if (languageVersion == null ||
- languageVersion < _firstVersionWithNullSafety) {
- return NullSafetyCompliance.notCompliant;
+ final languageVersion = pubspec.languageVersion;
+ if (languageVersion == null || !languageVersion.supportsNullSafety) {
+ final span =
+ _tryGetSpanFromYamlMap(pubspec.fields['environment'], 'sdk');
+ final where = span == null
+ ? 'in the sdk constraint in the enviroment key in its pubspec.yaml.'
+ : 'in its pubspec.yaml:\n${span.highlight()}';
+ return NullSafetyAnalysisResult(
+ NullSafetyCompliance.notCompliant,
+ 'package:${dependencyId.name} is not opted into null safety $where',
+ );
}
if (boundSource is CachedSource) {
@@ -131,50 +156,68 @@
for (final file in listDir(libDir,
recursive: true, includeDirs: false, includeHidden: true)) {
if (file.endsWith('.dart')) {
+ final fileUrl =
+ 'package:${dependencyId.name}/${path.relative(file, from: libDir)}';
final unitResult =
analysisSession.getParsedUnit(path.normalize(file));
if (unitResult == null || unitResult.errors.isNotEmpty) {
- return NullSafetyCompliance.analysisFailed;
+ return NullSafetyAnalysisResult(
+ NullSafetyCompliance.analysisFailed,
+ 'Could not analyze $fileUrl.');
}
if (unitResult.isPart) continue;
final languageVersionToken = unitResult.unit.languageVersionToken;
if (languageVersionToken == null) continue;
- if (Version(languageVersionToken.major,
- languageVersionToken.minor, 0) <
- _firstVersionWithNullSafety) {
- return NullSafetyCompliance.notCompliant;
+ final languageVersion = LanguageVersion.fromLanguageVersionToken(
+ languageVersionToken);
+ if (!languageVersion.supportsNullSafety) {
+ final sourceFile =
+ SourceFile.fromString(readTextFile(file), url: fileUrl);
+ final span = sourceFile.span(languageVersionToken.offset,
+ languageVersionToken.offset + languageVersionToken.length);
+ return NullSafetyAnalysisResult(
+ NullSafetyCompliance.notCompliant,
+ '$fileUrl is opting out of null safety:\n${span.highlight()}');
}
}
}
}
- return NullSafetyCompliance.compliant;
+ return NullSafetyAnalysisResult(NullSafetyCompliance.compliant, null);
});
- assert(packageInternallyGood != null);
- if (packageInternallyGood == NullSafetyCompliance.analysisFailed) {
- return NullSafetyCompliance.analysisFailed;
+ assert(packageInternalAnalysis != null);
+ if (packageInternalAnalysis.compliance ==
+ NullSafetyCompliance.analysisFailed) {
+ return packageInternalAnalysis;
}
- if (packageInternallyGood == NullSafetyCompliance.notCompliant) {
- allPackagesGood = false;
+ if (packageInternalAnalysis.compliance ==
+ NullSafetyCompliance.notCompliant) {
+ firstBadPackage ??= packageInternalAnalysis;
}
}
- if (allPackagesGood) return NullSafetyCompliance.compliant;
- final rootLanguageVersion = _languageVersion(
- await packageId.source.bind(_systemCache).describe(packageId));
- if (rootLanguageVersion != null &&
- rootLanguageVersion >= _firstVersionWithNullSafety) {
- return NullSafetyCompliance.apiOnly;
- }
- return NullSafetyCompliance.notCompliant;
- }
- /// Returns the language version specified by the dart sdk
- Version _languageVersion(Pubspec pubspec) {
- final sdkConstraint = pubspec.sdkConstraints['dart'];
- if (sdkConstraint is VersionRange) {
- final rangeMin = sdkConstraint.min;
- if (rangeMin == null) return null;
- return Version(rangeMin.major, rangeMin.minor, 0);
+ if (firstBadPackage == null) {
+ return NullSafetyAnalysisResult(NullSafetyCompliance.compliant, null);
}
- return null;
+ if (firstBadPackage.compliance == NullSafetyCompliance.analysisFailed) {
+ return firstBadPackage;
+ }
+ return NullSafetyAnalysisResult(
+ NullSafetyCompliance.mixed, firstBadPackage.reason);
}
}
+
+class NullSafetyAnalysisResult {
+ final NullSafetyCompliance compliance;
+
+ /// `null` if compliance == [NullSafetyCompliance.compliant].
+ final String reason;
+
+ NullSafetyAnalysisResult(this.compliance, this.reason);
+}
+
+SourceSpan _tryGetSpanFromYamlMap(Object map, String key) {
+ if (map is YamlMap) {
+ return map.nodes[key]?.span;
+ }
+ return null;
+}
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 8bcc3a9..b309e04 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -14,6 +14,7 @@
import 'exceptions.dart';
import 'feature.dart';
import 'io.dart';
+import 'language_version.dart';
import 'log.dart';
import 'package_name.dart';
import 'sdk.dart';
@@ -445,6 +446,17 @@
bool get isEmpty =>
name == null && version == Version.none && dependencies.isEmpty;
+ /// The language version implied by the sdk constraint.
+ ///
+ /// Given no or unbounded constraint we assume language version 1.0.
+ LanguageVersion get languageVersion {
+ final constraint = originalDartSdkConstraint;
+ if (constraint is VersionRange && constraint.min != null) {
+ return LanguageVersion.fromVersionRange(constraint);
+ }
+ return LanguageVersion(1, 0);
+ }
+
/// Loads the pubspec for a package located in [packageDir].
///
/// If [expectedName] is passed and the pubspec doesn't have a matching name
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index 770f68b..83f8df1 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -5,6 +5,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
+import 'package:pub/src/validator/null_safety_mixed_mode.dart';
import 'package:pub_semver/pub_semver.dart';
import 'entrypoint.dart';
@@ -136,6 +137,7 @@
FlutterPluginFormatValidator(entrypoint),
LanguageVersionValidator(entrypoint),
RelativeVersionNumberingValidator(entrypoint, serverUrl),
+ NullSafetyMixedModeValidator(entrypoint),
];
if (packageSize != null) {
validators.add(SizeValidator(entrypoint, packageSize));
diff --git a/lib/src/validator/language_version.dart b/lib/src/validator/language_version.dart
index f0fbdd3..e52a92a 100644
--- a/lib/src/validator/language_version.dart
+++ b/lib/src/validator/language_version.dart
@@ -6,11 +6,11 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:path/path.dart' as p;
-import 'package:pub_semver/pub_semver.dart';
import 'package:stack_trace/stack_trace.dart';
import '../dart.dart';
import '../entrypoint.dart';
+import '../language_version.dart';
import '../log.dart' as log;
import '../utils.dart';
import '../validator.dart';
@@ -28,17 +28,8 @@
@override
Future validate() async {
- final sdkConstraint = entrypoint.root.pubspec.originalDartSdkConstraint;
+ final declaredLanguageVersion = entrypoint.root.pubspec.languageVersion;
- /// If the sdk constraint is not a `VersionRange` something is wrong, and
- /// we cannot deduce the language version.
- ///
- /// This will hopefully be detected elsewhere.
- ///
- /// A single `Version` is also a `VersionRange`.
- if (sdkConstraint is! VersionRange) return;
-
- final packageSdkMinVersion = (sdkConstraint as VersionRange).min;
for (final path in ['lib', 'bin']
.map((path) => entrypoint.root.listFiles(beneath: path))
.expand((files) => files)
@@ -53,18 +44,15 @@
continue;
}
- final unitLanguageVersion = unit.languageVersionToken;
- if (unitLanguageVersion != null) {
- if (Version(unitLanguageVersion.major, unitLanguageVersion.minor, 0) >
- packageSdkMinVersion) {
- final packageLanguageVersionString =
- '${packageSdkMinVersion.major}.${packageSdkMinVersion.minor}';
- final unitLanguageVersionString =
- '${unitLanguageVersion.major}.${unitLanguageVersion.minor}';
+ final unitLanguageVersionToken = unit.languageVersionToken;
+ if (unitLanguageVersionToken != null) {
+ final unitLanguageVersion =
+ LanguageVersion.fromLanguageVersionToken(unitLanguageVersionToken);
+ if (unitLanguageVersion > declaredLanguageVersion) {
final relativePath = p.relative(path);
errors.add('$relativePath is declaring language version '
- '$unitLanguageVersionString that is newer than the SDK '
- 'constraint $packageLanguageVersionString declared in '
+ '$unitLanguageVersion that is newer than the SDK '
+ 'constraint $declaredLanguageVersion declared in '
'`pubspec.yaml`.');
}
}
diff --git a/lib/src/validator/null_safety_mixed_mode.dart b/lib/src/validator/null_safety_mixed_mode.dart
new file mode 100644
index 0000000..069b1c1
--- /dev/null
+++ b/lib/src/validator/null_safety_mixed_mode.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, 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 'dart:async';
+
+import 'package:path/path.dart' as p;
+
+import '../entrypoint.dart';
+import '../null_safety_analysis.dart';
+import '../package_name.dart';
+import '../validator.dart';
+
+/// Gives a warning when publishing a new version, if this package opts into
+/// null safety, but any of the dependencies do not.
+class NullSafetyMixedModeValidator extends Validator {
+ static const String guideUrl = 'https://dart.dev/null-safety/migration-guide';
+
+ NullSafetyMixedModeValidator(Entrypoint entrypoint) : super(entrypoint);
+
+ @override
+ Future<void> validate() async {
+ final pubspec = entrypoint.root.pubspec;
+ final declaredLanguageVersion = pubspec.languageVersion;
+ if (!declaredLanguageVersion.supportsNullSafety) {
+ return;
+ }
+ final analysisResult = await NullSafetyAnalysis(entrypoint.cache)
+ .nullSafetyCompliance(PackageId(
+ entrypoint.root.name,
+ entrypoint.cache.sources.path,
+ entrypoint.root.version,
+ {'relative': false, 'path': p.absolute(entrypoint.root.dir)}));
+
+ if (analysisResult.compliance == NullSafetyCompliance.mixed) {
+ warnings.add('''
+This package is opting into null-safety, but a dependency or file is not.
+
+${analysisResult.reason}
+
+Note that by publishing with non-migrated dependencies your package may be
+broken at any time if one of your dependencies migrates without a breaking
+change release.
+
+We highly recommend that you wait until all of your dependencies have been
+migrated before publishing.
+
+Run `pub outdated --mode=null-safety` for more information about the state of
+dependencies.
+
+See $guideUrl
+for more information about migrating.
+''');
+ }
+ }
+}
diff --git a/lib/src/validator/relative_version_numbering.dart b/lib/src/validator/relative_version_numbering.dart
index 5e227fc..3d69038 100644
--- a/lib/src/validator/relative_version_numbering.dart
+++ b/lib/src/validator/relative_version_numbering.dart
@@ -8,6 +8,7 @@
import '../entrypoint.dart';
import '../exceptions.dart';
+import '../language_version.dart';
import '../package_name.dart';
import '../pubspec.dart';
import '../validator.dart';
@@ -76,11 +77,6 @@
if (constraintMin == null) return false;
- final languageVersion =
- Version(constraintMin.major, constraintMin.minor, 0);
-
- return languageVersion >= _firstVersionSupportingNullSafety;
+ return LanguageVersion.fromVersionRange(sdkConstraint).supportsNullSafety;
}
-
- static final _firstVersionSupportingNullSafety = Version.parse('2.10.0');
}
diff --git a/test/outdated/goldens/circular_dependencies.txt b/test/outdated/goldens/circular_dependencies.txt
index 9a521c2..6bbc4e7 100644
--- a/test/outdated/goldens/circular_dependencies.txt
+++ b/test/outdated/goldens/circular_dependencies.txt
@@ -85,7 +85,7 @@
Showing packages where the current version doesn't fully support null safety.
Dependencies Current Upgradable Resolvable Latest
-foo ?1.2.3 ?1.3.0 ?1.3.0 ?1.3.0
+foo ✗1.2.3 ✗1.3.0 ✗1.3.0 ✗1.3.0
dev_dependencies: all fully support null safety
@@ -103,19 +103,19 @@
"package": "foo",
"current": {
"version": "1.2.3",
- "nullSafety": null
+ "nullSafety": false
},
"upgradable": {
"version": "1.3.0",
- "nullSafety": null
+ "nullSafety": false
},
"resolvable": {
"version": "1.3.0",
- "nullSafety": null
+ "nullSafety": false
},
"latest": {
"version": "1.3.0",
- "nullSafety": null
+ "nullSafety": false
}
}
]
diff --git a/test/outdated/goldens/dependency_overrides_no_solution.txt b/test/outdated/goldens/dependency_overrides_no_solution.txt
index cea1ca0..f8ec65a 100644
--- a/test/outdated/goldens/dependency_overrides_no_solution.txt
+++ b/test/outdated/goldens/dependency_overrides_no_solution.txt
@@ -111,8 +111,8 @@
Showing packages where the current version doesn't fully support null safety.
Dependencies Current Upgradable Resolvable Latest
-bar ?1.0.0 (overridden) ?1.0.0 (overridden) ?1.0.0 (overridden) ?2.0.0
-foo ?1.0.0 (overridden) ?1.0.0 (overridden) ?1.0.0 (overridden) ?2.0.0
+bar ✗1.0.0 (overridden) ✗1.0.0 (overridden) ✗1.0.0 (overridden) ✗2.0.0
+foo ✗1.0.0 (overridden) ✗1.0.0 (overridden) ✗1.0.0 (overridden) ✗2.0.0
dev_dependencies: all fully support null safety
@@ -131,21 +131,21 @@
"current": {
"version": "1.0.0",
"overridden": true,
- "nullSafety": null
+ "nullSafety": false
},
"upgradable": {
"version": "1.0.0",
"overridden": true,
- "nullSafety": null
+ "nullSafety": false
},
"resolvable": {
"version": "1.0.0",
"overridden": true,
- "nullSafety": null
+ "nullSafety": false
},
"latest": {
"version": "2.0.0",
- "nullSafety": null
+ "nullSafety": false
}
},
{
@@ -153,21 +153,21 @@
"current": {
"version": "1.0.0",
"overridden": true,
- "nullSafety": null
+ "nullSafety": false
},
"upgradable": {
"version": "1.0.0",
"overridden": true,
- "nullSafety": null
+ "nullSafety": false
},
"resolvable": {
"version": "1.0.0",
"overridden": true,
- "nullSafety": null
+ "nullSafety": false
},
"latest": {
"version": "2.0.0",
- "nullSafety": null
+ "nullSafety": false
}
}
]
diff --git a/test/outdated/goldens/mutually_incompatible.txt b/test/outdated/goldens/mutually_incompatible.txt
index 89ba210..13febb5 100644
--- a/test/outdated/goldens/mutually_incompatible.txt
+++ b/test/outdated/goldens/mutually_incompatible.txt
@@ -105,8 +105,8 @@
Showing packages where the current version doesn't fully support null safety.
Dependencies Current Upgradable Resolvable Latest
-bar ✗1.0.0 ✗1.0.0 ✗1.0.0 ?2.0.0
-foo ✗1.0.0 ✗1.0.0 ✗1.0.0 ?2.0.0
+bar ✗1.0.0 ✗1.0.0 ✗1.0.0 ✗2.0.0
+foo ✗1.0.0 ✗1.0.0 ✗1.0.0 ✗2.0.0
dev_dependencies: all fully support null safety
@@ -136,7 +136,7 @@
},
"latest": {
"version": "2.0.0",
- "nullSafety": null
+ "nullSafety": false
}
},
{
@@ -155,7 +155,7 @@
},
"latest": {
"version": "2.0.0",
- "nullSafety": null
+ "nullSafety": false
}
}
]
diff --git a/test/outdated/outdated_test.dart b/test/outdated/outdated_test.dart
index 1acc869..3795ad5 100644
--- a/test/outdated/outdated_test.dart
+++ b/test/outdated/outdated_test.dart
@@ -196,50 +196,50 @@
..serve('foo', '1.0.0', deps: {
'bar': '^1.0.0'
}, pubspec: {
- 'environment': {'sdk': '>=2.9.0 < 3.0,0'}
+ 'environment': {'sdk': '>=2.9.0 < 3.0.0'}
})
..serve('bar', '1.0.0', pubspec: {
- 'environment': {'sdk': '>=2.9.0 < 3.0,0'}
+ 'environment': {'sdk': '>=2.9.0 < 3.0.0'}
})
..serve('foo', '2.0.0', deps: {
'bar': '^1.0.0'
}, pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'}
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'}
})
..serve('bar', '2.0.0', pubspec: {
- 'environment': {'sdk': '>=2.11.0 < 3.0,0'}
+ 'environment': {'sdk': '>=2.11.0 < 3.0.0'}
})
..serve('file_opts_out', '1.0.0', pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
}, contents: [
d.dir('lib', [d.file('main.dart', '// @dart = 2.9\n')])
])
..serve('file_opts_out', '2.0.0', pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
})
..serve('fails_analysis', '1.0.0', pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
}, contents: [
d.dir('lib', [d.file('main.dart', 'syntax error\n')])
])
..serve('fails_analysis', '2.0.0', pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
})
..serve('file_in_dependency_opts_out', '1.0.0', deps: {
'file_opts_out': '^1.0.0'
}, pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
})
..serve('file_in_dependency_opts_out', '2.0.0', pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
})
..serve('fails_analysis_in_dependency', '1.0.0', deps: {
'fails_analysis': '^1.0.0'
}, pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
})
..serve('fails_analysis_in_dependency', '2.0.0', pubspec: {
- 'environment': {'sdk': '>=2.10.0 < 3.0,0'},
+ 'environment': {'sdk': '>=2.10.0 < 3.0.0'},
}),
);
await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.11.0'});
diff --git a/test/validator/null_safety_mixed_mode_test.dart b/test/validator/null_safety_mixed_mode_test.dart
new file mode 100644
index 0000000..15000a9
--- /dev/null
+++ b/test/validator/null_safety_mixed_mode_test.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2020, 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/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+Future<void> expectValidation(error, int exitCode) async {
+ await runPub(
+ error: error,
+ args: ['publish', '--dry-run'],
+ environment: {'_PUB_TEST_SDK_VERSION': '2.10.0'},
+ workingDirectory: d.dir(appPath).io.path,
+ exitCode: exitCode);
+}
+
+Future<void> setup(
+ {String sdkConstraint,
+ Map dependencies = const {},
+ List<d.Descriptor> extraFiles = const []}) async {
+ await d.validPackage.create();
+ await d.dir(appPath, [
+ d.pubspec({
+ 'name': 'test_pkg',
+ 'description':
+ 'A just long enough decription to fit the requirement of 60 characters',
+ 'homepage': 'https://example.com/',
+ 'version': '1.0.0',
+ 'environment': {'sdk': sdkConstraint},
+ 'dependencies': dependencies,
+ }),
+ ...extraFiles,
+ ]).create();
+
+ await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.10.0'});
+}
+
+void main() {
+ group('should consider a package valid if it', () {
+ test('is not opting in to null-safety, but depends on package that is',
+ () async {
+ await servePackages(
+ (server) => server.serve(
+ 'foo',
+ '0.0.1',
+ pubspec: {
+ 'environment': {'sdk': '>=2.10.0<3.0.0'}
+ },
+ ),
+ );
+
+ await setup(
+ sdkConstraint: '>=2.9.0 <3.0.0', dependencies: {'foo': '^0.0.1'});
+ await expectValidation(contains('Package has 0 warnings.'), 0);
+ });
+ test('is opting in to null-safety and depends on package that is',
+ () async {
+ await servePackages(
+ (server) => server.serve(
+ 'foo',
+ '0.0.1',
+ pubspec: {
+ 'environment': {'sdk': '>=2.10.0<3.0.0'}
+ },
+ ),
+ );
+
+ await setup(
+ sdkConstraint: '>=2.10.0 <3.0.0', dependencies: {'foo': '^0.0.1'});
+ await expectValidation(contains('Package has 0 warnings.'), 0);
+ });
+ });
+
+ group('should consider a package invalid if it', () {
+ test('is opting in to null-safety, but depends on package that is not',
+ () async {
+ await servePackages(
+ (server) => server.serve(
+ 'foo',
+ '0.0.1',
+ pubspec: {
+ 'environment': {'sdk': '>=2.9.0<3.0.0'}
+ },
+ ),
+ );
+
+ await setup(
+ sdkConstraint: '>=2.10.0 <3.0.0', dependencies: {'foo': '^0.0.1'});
+ await expectValidation(
+ allOf(
+ contains(
+ 'package:foo is not opted into null safety in its pubspec.yaml:'),
+ contains('Package has 1 warning.'),
+ ),
+ 65);
+ });
+
+ test('is opting in to null-safety, but has file opting out', () async {
+ await setup(sdkConstraint: '>=2.10.0 <3.0.0', extraFiles: [
+ d.dir('lib', [d.file('a.dart', '// @dart = 2.9\n')])
+ ]);
+ await expectValidation(
+ allOf(
+ contains('package:test_pkg/a.dart is opting out of null safety:'),
+ contains('Package has 1 warning.'),
+ ),
+ 65);
+ });
+
+ test(
+ 'is opting in to null-safety, but depends on package has file opting out',
+ () async {
+ await servePackages(
+ (server) => server.serve('foo', '0.0.1', pubspec: {
+ 'environment': {'sdk': '>=2.10.0<3.0.0'}
+ }, contents: [
+ d.dir('lib', [
+ d.file('foo.dart', '''
+// @dart = 2.9
+ ''')
+ ])
+ ]),
+ );
+
+ await setup(
+ sdkConstraint: '>=2.10.0 <3.0.0', dependencies: {'foo': '^0.0.1'});
+ await expectValidation(
+ allOf(
+ contains('package:foo/foo.dart is opting out of null safety:'),
+ contains('Package has 1 warning.'),
+ ),
+ 65);
+ });
+ });
+}
diff --git a/test/validator/relative_version_numbering_test.dart b/test/validator/relative_version_numbering_test.dart
index a723706..7adcb1f 100644
--- a/test/validator/relative_version_numbering_test.dart
+++ b/test/validator/relative_version_numbering_test.dart
@@ -26,7 +26,6 @@
]).create();
await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.10.0'});
- print(await d.file('.dart_tool/package_config.json').read());
}
void main() {