Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support 'direct-overridden' dependency type when checking licenses #931

Merged
merged 13 commits into from
Dec 22, 2023
Merged
34 changes: 20 additions & 14 deletions lib/src/commands/packages/commands/check/commands/licenses.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import 'package:package_config/package_config.dart' as package_config;
// ignore: implementation_imports
import 'package:pana/src/license_detection/license_detector.dart' as detector;
import 'package:path/path.dart' as path;
import 'package:pubspec_lock/pubspec_lock.dart';
import 'package:very_good_cli/src/pub_license/spdx_license.gen.dart';
import 'package:very_good_cli/src/pubspec_lock/pubspec_lock.dart';

/// Overrides the [package_config.findPackageConfig] function for testing.
@visibleForTesting
Expand Down Expand Up @@ -86,12 +86,14 @@ class PackagesCheckLicensesCommand extends Command<int> {
allowed: [
'direct-main',
'direct-dev',
'direct-overridden',
'transitive',
],
allowedHelp: {
'direct-main': 'Check for direct main dependencies.',
'direct-dev': 'Check for direct dev dependencies.',
'transitive': 'Check for transitive dependencies.',
'direct-overridden': 'Check for direct overridden dependencies.',
},
defaultsTo: ['direct-main'],
)
Expand Down Expand Up @@ -182,19 +184,20 @@ class PackagesCheckLicensesCommand extends Command<int> {
}

final filteredDependencies = pubspecLock.packages.where((dependency) {
// ignore: invalid_use_of_protected_member
final isPubHosted = dependency.hosted != null;
if (!isPubHosted) return false;
if (!dependency.isPubHosted) return false;

if (skippedPackages.contains(dependency.package())) return false;
if (skippedPackages.contains(dependency.name)) return false;

final dependencyType = dependency.type();
final dependencyType = dependency.type;
return (dependencyTypes.contains('direct-main') &&
dependencyType == DependencyType.direct) ||
dependencyType == PubspecLockPackageDependencyType.directMain) ||
(dependencyTypes.contains('direct-dev') &&
dependencyType == DependencyType.development) ||
dependencyType == PubspecLockPackageDependencyType.directDev) ||
(dependencyTypes.contains('transitive') &&
dependencyType == DependencyType.transitive);
dependencyType == PubspecLockPackageDependencyType.transitive) ||
(dependencyTypes.contains('direct-overridden') &&
dependencyType ==
PubspecLockPackageDependencyType.directOverridden);
});

if (filteredDependencies.isEmpty) {
Expand All @@ -221,7 +224,7 @@ class PackagesCheckLicensesCommand extends Command<int> {
'''Collecting licenses from ${licenses.length + 1} out of ${filteredDependencies.length} ${filteredDependencies.length == 1 ? 'package' : 'packages'}''',
);

final dependencyName = dependency.package();
final dependencyName = dependency.name;
final cachePackageEntry = packageConfig.packages
.firstWhereOrNull((package) => package.name == dependencyName);
if (cachePackageEntry == null) {
Expand Down Expand Up @@ -323,11 +326,14 @@ class PackagesCheckLicensesCommand extends Command<int> {
/// If [pubspecLockFile] is not readable or fails to be parsed, `null` is
/// returned.
PubspecLock? _tryParsePubspecLock(File pubspecLockFile) {
try {
return pubspecLockFile.readAsStringSync().loadPubspecLockFromYaml();
} catch (e) {
return null;
if (pubspecLockFile.existsSync()) {
final content = pubspecLockFile.readAsStringSync();
try {
return PubspecLock.fromString(content);
} catch (_) {}
}

return null;
}

/// Attempts to find a [package_config.PackageConfig] using
Expand Down
186 changes: 186 additions & 0 deletions lib/src/pubspec_lock/pubspec_lock.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/// A simple parser for pubspec.lock files.
///
/// This is used by the `packages check license` command to check the type and
/// source of the dependencies to analyze. Hence it is not a complete parser,
alestiago marked this conversation as resolved.
Show resolved Hide resolved
/// it only parses the information that is needed for the
/// `packages check license` command.
library pubspec_lock;

import 'dart:collection';

import 'package:equatable/equatable.dart';
import 'package:yaml/yaml.dart';

/// {@template PubspecLockParseException}
/// Thrown when a [PubspecLock] fails to parse.
/// {@endtemplate}
class PubspecLockParseException implements Exception {
/// {@macro PubspecLockParseException}
const PubspecLockParseException();
}

/// {@template PubspecLock}
/// A representation of a pubspec.lock file.
/// {@endtemplate}
class PubspecLock {
const PubspecLock._({
required this.packages,
});

/// Parses a [PubspecLock] from a string.
///
/// If no packages are found, an empty [PubspecLock] is returned. Those
/// packages entries that cannot be parsed are ignored.
///
/// It throws a [PubspecLockParseException] if the string cannot be parsed
/// as a [YamlMap].
factory PubspecLock.fromString(String content) {
late final YamlMap yaml;
try {
yaml = loadYaml(content) as YamlMap;
} catch (_) {
throw const PubspecLockParseException();
}

if (!yaml.containsKey('packages')) {
return PubspecLock.empty;
}

final packages = yaml['packages'] as YamlMap;

final parsedPackages = <PubspecLockPackage>[];
for (final entry in packages.entries) {
try {
final package = PubspecLockPackage.fromYamlMap(
name: entry.key as String,
data: entry.value as YamlMap,
);
parsedPackages.add(package);
} catch (_) {
// Ignore those packages that for some reason cannot be parsed.
}
}

return PubspecLock._(
packages: UnmodifiableListView(parsedPackages),
);
}

/// An empty [PubspecLock].
static PubspecLock empty = PubspecLock._(
packages: UnmodifiableListView([]),
);

/// All the dependencies in the pubspec.lock file.
final UnmodifiableListView<PubspecLockPackage> packages;
}

/// {@template PubspecLockDependency}
/// A representation of a dependency in a pubspec.lock file.
/// {@endtemplate}
class PubspecLockPackage extends Equatable {
/// {@macro PubspecLockDependency}
const PubspecLockPackage({
required this.name,
required this.type,
required this.isPubHosted,
});

/// Parses a [PubspecLockPackage] from a [YamlMap].
factory PubspecLockPackage.fromYamlMap({
required String name,
required YamlMap data,
}) {
final dependency = data['dependency'] as String;
final dependencyType = PubspecLockPackageDependencyType.parse(dependency);

final source = data['source'] as String;
late final bool isPubHosted;
if (source == 'hosted') {
final description = data['description'] as YamlMap;
final url = description['url'] as String;
isPubHosted = url == 'https://pub.dev';
} else {
isPubHosted = false;
}

return PubspecLockPackage(
name: name,
type: dependencyType,
isPubHosted: isPubHosted,
);
}

/// The name of the dependency.
final String name;

/// {@macro PubspecLockDependencyType}
final PubspecLockPackageDependencyType type;

/// Whether the dependency is hosted on pub.dev or not.
final bool isPubHosted;

@override
List<Object?> get props => [type, isPubHosted];
}

/// {@template PubspecLockDependencyType}
/// The type of a [PubspecLockPackage].
/// {@endtemplate}
enum PubspecLockPackageDependencyType {
/// Another package that your package needs to work.
///
/// See also:
///
/// * [Dart's dependency documentation](https://dart.dev/tools/pub/dependencies)
directMain._('direct main'),

/// Another package that your package needs during development.
///
/// See also:
///
/// * [Dart's developer dependency documentation](https://dart.dev/tools/pub/dependencies#dev-dependencies)
directDev._('direct dev'),

/// A dependency that your package indirectly uses because one of its
/// dependencies requires it.
///
/// See also:
///
/// * [Dart's transitive dependency documentation](https://dart.dev/tools/pub/glossary#transitive-)
transitive._('transitive'),

/// A dependency that your package overrides that is not already a
/// `direct main` or `direct dev` dependency.
///
/// See also:
///
/// * [Dart's dependency override documentation](https://dart.dev/tools/pub/dependencies#dependency-overrides)
directOverridden._('direct overridden');

const PubspecLockPackageDependencyType._(this.value);

/// Parses a [PubspecLockPackageDependencyType] from a string.
///
/// Throws an [ArgumentError] if the string is not a valid dependency type.
factory PubspecLockPackageDependencyType.parse(String value) {
if (_valueMap.containsKey(value)) {
return _valueMap[value]!;
}

throw ArgumentError.value(
value,
'value',
'Invalid PubspecLockPackageDependencyType value.',
);
}

static Map<String, PubspecLockPackageDependencyType> _valueMap = {
for (final type in PubspecLockPackageDependencyType.values)
type.value: type,
};

/// The string representation of the [PubspecLockPackageDependencyType]
/// as it appears in a pubspec.lock file.
final String value;
}
11 changes: 2 additions & 9 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
args: ^2.1.0
cli_completion: ^0.4.0
collection: ^1.17.1
equatable: ^2.0.5
glob: ^2.0.2
lcov_parser: ^0.1.2
mason: 0.1.0-dev.51
Expand All @@ -23,11 +24,11 @@ dependencies:
pana: 0.21.45 # Very Good CLI is using private PANA's license detector that might break in a minor version update.
path: ^1.8.0
pub_updater: ">=0.3.1 <0.5.0"
pubspec_lock: ^3.0.2
pubspec_parse: ^1.2.0
stack_trace: ^1.10.0
universal_io: ^2.0.4
very_good_test_runner: ^0.1.2
yaml: ^3.1.2

dev_dependencies:
build_runner: ^2.4.6
Expand All @@ -37,13 +38,5 @@ dev_dependencies:
test: ^1.24.3
very_good_analysis: ^5.1.0

dependency_overrides:
pubspec_lock:
# Override to support "direct overridden" dependency parsing, remove
# once the issue is fixed: https://github.com/alexei-sintotski/pubspec_lock/issues/33
git:
url: https://github.com/PieterAelse/pubspec_lock
ref: 93ecf65e537f6202dc28c694143bc6a176d603ef

executables:
very_good:
Loading