Skip to content

Commit

Permalink
feat: support 'direct-overridden' dependency type when checking licen…
Browse files Browse the repository at this point in the history
…ses (#931)

* fix: avoid failing with "direct direct overridden" dependencies

* refactor: remove unused code

* feat: support 'direct-overridden' dependency type

* chore: update overridden path

* chore: override in e2e

* refactor: remove pubspec_lock dependency

* test: included not pub hosted sample

* test: used $ for class names

* refactor: removed overridden dependency

* test: PubspecLockPackageDependencyType.parse

* Update lib/src/pubspec_lock/pubspec_lock.dart
  • Loading branch information
alestiago committed Dec 22, 2023
1 parent 22778ed commit 466e0a2
Show file tree
Hide file tree
Showing 5 changed files with 489 additions and 28 deletions.
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,
/// 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

0 comments on commit 466e0a2

Please sign in to comment.