diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart index 16e2f1730..54f04d48a 100644 --- a/lib/src/global_packages.dart +++ b/lib/src/global_packages.dart @@ -13,6 +13,7 @@ import 'entrypoint.dart'; import 'exceptions.dart'; import 'executable.dart' as exec; import 'io.dart'; +import 'language_version.dart'; import 'lock_file.dart'; import 'log.dart' as log; import 'package.dart'; @@ -114,6 +115,7 @@ class GlobalPackages { if (ref != null) 'ref': ref, }, containingDescription: RootDescription(p.current), + languageVersion: LanguageVersion.fromVersion(sdk.version), ); } on FormatException catch (e) { throw ApplicationException(e.message); diff --git a/lib/src/language_version.dart b/lib/src/language_version.dart index 578744458..f7d2b9d3f 100644 --- a/lib/src/language_version.dart +++ b/lib/src/language_version.dart @@ -72,6 +72,9 @@ class LanguageVersion implements Comparable { bool get supportsWorkspaces => this >= firstVersionWithWorkspaces; + bool get forbidsUnknownDescriptionKeys => + this >= firstVersionForbidingUnknownDescriptionKeys; + /// Minimum language version at which short hosted syntax is supported. /// /// This allows `hosted` dependencies to be expressed as: @@ -109,6 +112,8 @@ class LanguageVersion implements Comparable { static const firstVersionWithNullSafety = LanguageVersion(2, 12); static const firstVersionWithShorterHostedSyntax = LanguageVersion(2, 15); static const firstVersionWithWorkspaces = LanguageVersion(3, 5); + static const firstVersionForbidingUnknownDescriptionKeys = + LanguageVersion(3, 7); /// Transform language version to string that can be parsed with /// [LanguageVersion.parse]. diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart index 54f4dd2e1..2c753c653 100644 --- a/lib/src/source/git.dart +++ b/lib/src/source/git.dart @@ -38,7 +38,7 @@ class GitSource extends CachedSource { String name, Object? description, { Description? containingDescription, - LanguageVersion? languageVersion, + required LanguageVersion languageVersion, }) { String url; String? ref; @@ -72,6 +72,16 @@ class GitSource extends CachedSource { 'string.'); } path = descriptionPath; + + if (languageVersion.forbidsUnknownDescriptionKeys) { + for (final key in description.keys) { + if (!['url', 'ref', 'path'].contains(key)) { + throw FormatException( + 'Unknown key "$key" in description.', + ); + } + } + } } final containingDir = switch (containingDescription) { diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart index c7d5a03b6..cb31c5931 100644 --- a/lib/src/source/hosted.dart +++ b/lib/src/source/hosted.dart @@ -381,6 +381,15 @@ class HostedSource extends CachedSource { } final url = u ?? defaultUrl; + if (languageVersion.forbidsUnknownDescriptionKeys) { + for (final key in description.keys) { + if (!['url', 'name'].contains(key)) { + throw FormatException( + 'Unknown key "$key" in description.', + ); + } + } + } return HostedDescription(name, url as String); } diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart index c181fcc0c..f0f5a0f27 100644 --- a/test/pubspec_test.dart +++ b/test/pubspec_test.dart @@ -1133,5 +1133,39 @@ name: 'foo' ); }); }); + test( + 'Throws after language 3.7 ' + 'if using unknown keys in dependency description', () { + expectPubspecException( + ''' +environment: + sdk: 3.7.0 +dependencies: + foo: + hosted: + name: 'foo' + url: https://pub.dev/ + someOtherProperty: 'smile' +''', + (pubspec) => pubspec.dependencies, + expectedContains: 'Unknown key "someOtherProperty" in description.', + ); + + expectPubspecException( + ''' +environment: + sdk: 3.7.0 +dependencies: + test: + git: + ref: 'v1.0.0' + url: https://github.com/dart-lang/test + path: 'pkgs/test' + someOtherProperty: 'smile' +''', + (pubspec) => pubspec.dependencies, + expectedContains: 'Unknown key "someOtherProperty" in description.', + ); + }); }); } diff --git a/test/unknown_properties_in_description_test.dart b/test/unknown_properties_in_description_test.dart new file mode 100644 index 000000000..137e89e4e --- /dev/null +++ b/test/unknown_properties_in_description_test.dart @@ -0,0 +1,104 @@ +// Copyright (c) 2024, 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:pub/src/exit_codes.dart'; +import 'package:test/test.dart'; + +import 'descriptor.dart' as d; +import 'test_pub.dart'; + +void main() { + test('Ignores additional properties in descriptions before 3.7', () async { + final server = await servePackages(); + server.serve( + 'foo', + '1.0.0', + sdk: '^3.6.0', + deps: { + 'bar': { + 'hosted': {'url': server.url, 'unknown': 11}, + }, + }, + ); + server.serve('bar', '1.0.0'); + await d.appDir( + pubspec: { + 'environment': {'sdk': '^3.6.0'}, + }, + dependencies: { + 'foo': { + 'hosted': {'url': server.url, 'unknown': 11}, + 'version': '^1.0.0', + }, + }, + ).create(); + await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'}); + }); + + test('Detects unknown attributes in descriptions in root project after 3.7', + () async { + final server = await servePackages(); + server.serve( + 'foo', + '1.0.0', + sdk: '^3.6.0', + deps: { + 'bar': { + 'hosted': {'url': server.url, 'unknown': 11}, + }, + }, + ); + server.serve('bar', '1.0.0'); + await d.appDir( + pubspec: { + 'environment': {'sdk': '^3.7.0'}, + }, + dependencies: { + 'foo': { + 'hosted': {'url': server.url, 'unknown': 11}, + 'version': '^1.0.0', + }, + }, + ).create(); + await pubGet( + environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'}, + error: contains('Invalid description in the "myapp" pubspec ' + 'on the "foo" dependency: Unknown key "unknown" in description.'), + exitCode: DATA, + ); + }); + + test('Detects unknown attributes in descriptions in dependency after 3.7', + () async { + final server = await servePackages(); + server.serve( + 'foo', + '1.0.0', + sdk: '^3.7.0', + deps: { + 'bar': { + 'hosted': {'url': server.url, 'unknown': 11}, + }, + }, + ); + server.serve('bar', '1.0.0'); + await d.appDir( + pubspec: { + 'environment': {'sdk': '^3.6.0'}, + }, + dependencies: { + 'foo': { + 'hosted': {'url': server.url, 'unknown': 11}, + 'version': '^1.0.0', + }, + }, + ).create(); + await pubGet( + environment: {'_PUB_TEST_SDK_VERSION': '3.7.0'}, + error: contains('Invalid description in the "foo" pubspec ' + 'on the "bar" dependency: Unknown key "unknown" in description.'), + exitCode: DATA, + ); + }); +}