Skip to content

Commit 98ab617

Browse files
committed
chore: review
1 parent 2e1d2f9 commit 98ab617

8 files changed

Lines changed: 102 additions & 105 deletions

File tree

lib/src/commands/create/commands/create_subcommand.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,17 @@ mixin MultiTemplates on CreateSubCommand {
346346

347347
return templates.firstWhere(
348348
(template) => template.name == templateName,
349-
orElse: () => usageException(
350-
'"$templateName" is not an allowed value for option "--template".',
351-
),
349+
orElse: () {
350+
// When the value came from `very_good.yaml` rather than the command
351+
// line, point the user at the config key instead of the CLI option so
352+
// they debug the right place.
353+
final source = argResults.wasParsed('template')
354+
? 'option "--template"'
355+
: 'the `create.template` key in `$veryGoodConfigFileName`';
356+
usageException(
357+
'"$templateName" is not an allowed value for $source.',
358+
);
359+
},
352360
);
353361
}
354362
}

lib/src/commands/create/create.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,10 @@ class CreateCommand extends Command<int> {
9393
@override
9494
String get invocation =>
9595
'very_good create <subcommand> <project-name> [arguments]';
96+
97+
/// The names of the subcommands that accept the `--workspace` flag.
98+
Set<String> get workspaceSubcommandNames => subcommands.entries
99+
.where((entry) => entry.value is Workspace)
100+
.map((entry) => entry.key)
101+
.toSet();
96102
}

lib/src/commands/dart/commands/dart_test_command.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ class DartTestCommand extends Command<int> {
283283
help:
284284
'Whether to collect coverage from imported files only or all '
285285
'files.',
286-
allowed: ['imports', 'all'],
286+
allowed: collectCoverageFromAllowedValues,
287287
defaultsTo: 'imports',
288288
valueHelp: 'imports|all',
289289
)

lib/src/commands/packages/commands/check/commands/licenses.dart

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,7 @@ class PackagesCheckLicensesCommand extends Command<int> {
157157
..addMultiOption(
158158
'dependency-type',
159159
help: 'The type of dependencies to check licenses for.',
160-
allowed: [
161-
'direct-main',
162-
'direct-dev',
163-
'direct-overridden',
164-
'transitive',
165-
],
160+
allowed: dependencyTypeAllowedValues,
166161
allowedHelp: {
167162
'direct-main': 'Check for direct main dependencies.',
168163
'direct-dev': 'Check for direct dev dependencies.',
@@ -183,7 +178,7 @@ class PackagesCheckLicensesCommand extends Command<int> {
183178
..addOption(
184179
'reporter',
185180
help: 'Lists all licenses.',
186-
allowed: ['text', 'csv'],
181+
allowed: reporterAllowedValues,
187182
allowedHelp: {
188183
'text': 'Lists licenses without a specific format.',
189184
'csv': 'Lists licenses in a CSV format.',

lib/src/commands/test/test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ class TestCommand extends Command<int> {
333333
help:
334334
'Whether to collect coverage from imported files only or all '
335335
'files.',
336-
allowed: ['imports', 'all'],
336+
allowed: collectCoverageFromAllowedValues,
337337
defaultsTo: 'imports',
338338
valueHelp: 'imports|all',
339339
)

lib/src/very_good_config/very_good_config.dart

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -107,42 +107,23 @@ class VeryGoodConfig extends Equatable {
107107
/// Loads the closest [VeryGoodConfig] by searching [directory] and each of
108108
/// its ancestors, from the innermost directory outward.
109109
///
110-
/// [directory] is resolved to an absolute path before the walk, so a relative
111-
/// [directory] is searched relative to the current working directory.
112-
///
113-
/// This allows a single repository-wide `very_good.yaml` at the project root
114-
/// to apply to commands run from any nested package directory. The first
115-
/// configuration file encountered wins; ancestors are not merged.
116-
///
117110
/// Returns [VeryGoodConfig.empty] when no configuration file is found.
118-
/// Throws a [VeryGoodConfigParseException] when the closest file exists but
119-
/// cannot be parsed.
120-
factory VeryGoodConfig.loadFromClosestAncestor(Directory directory) {
121-
var current = directory.absolute;
122-
while (true) {
123-
final config = _loadFromDirectory(current);
124-
if (config != null) return config;
125-
final parent = current.parent;
126-
if (parent.path == current.path) return VeryGoodConfig.empty;
127-
current = parent;
128-
}
129-
}
130-
131-
/// Loads the closest [VeryGoodConfig] to [directory].
132111
///
133112
/// On a parse failure, logs a formatted error via [logger] and returns
134113
/// `null`, signalling that the caller should exit with a config error.
135-
///
136-
/// Centralizes the load-and-report contract shared by every command that
137-
/// reads `very_good.yaml`, so the error message and exit behavior stay
138-
/// consistent.
139114
static VeryGoodConfig? load(Directory directory, {required Logger logger}) {
140115
try {
141-
return VeryGoodConfig.loadFromClosestAncestor(directory);
116+
var current = directory.absolute;
117+
while (true) {
118+
final config = _loadFromDirectory(current);
119+
if (config != null) return config;
120+
final parent = current.parent;
121+
if (parent.path == current.path) return VeryGoodConfig.empty;
122+
current = parent;
123+
}
142124
} on VeryGoodConfigParseException catch (e) {
143125
logger.err(
144-
'Could not read `$veryGoodConfigFileName`.\n'
145-
'${e.message}',
126+
'Could not read `$veryGoodConfigFileName`.\n${e.message}',
146127
);
147128
return null;
148129
}
@@ -717,35 +698,48 @@ String? _minCoverage(Object? value) {
717698
return asString;
718699
}
719700

701+
/// The values accepted by the `collect-coverage-from` option, shared between
702+
/// the CLI argument parser and the `very_good.yaml` validator so they cannot
703+
/// drift apart.
704+
const collectCoverageFromAllowedValues = ['imports', 'all'];
705+
706+
/// The dependency types accepted by `very_good packages check licenses`, shared
707+
/// between the CLI argument parser and the `very_good.yaml` validator so they
708+
/// cannot drift apart.
709+
const dependencyTypeAllowedValues = [
710+
'direct-main',
711+
'direct-dev',
712+
'direct-overridden',
713+
'transitive',
714+
];
715+
716+
/// The values accepted by the license `reporter` option, shared between the
717+
/// CLI argument parser and the `very_good.yaml` validator so they cannot drift
718+
/// apart.
719+
const reporterAllowedValues = ['text', 'csv'];
720+
720721
/// Validates and returns the `collect_coverage_from` value.
721722
///
722723
/// Accepts only `imports` or `all`.
723724
String? _collectCoverageFrom(Object? value) {
724725
if (value == null) return null;
725-
if (value != 'imports' && value != 'all') {
726+
if (!collectCoverageFromAllowedValues.contains(value)) {
726727
throw FormatException('Expected `imports` or `all` but got `$value`.');
727728
}
728729
return value as String;
729730
}
730731

731-
/// The dependency types accepted by `very_good packages check licenses`.
732-
const _dependencyTypes = [
733-
'direct-main',
734-
'direct-dev',
735-
'direct-overridden',
736-
'transitive',
737-
];
738-
739732
/// Validates and returns the `dependency_type` value.
740733
///
741734
/// Accepts only the values allowed by the CLI option.
742735
List<String>? _dependencyType(Object? value) {
743736
final values = _stringList(value);
744737
if (values == null) return null;
745738
for (final value in values) {
746-
if (!_dependencyTypes.contains(value)) {
739+
if (!dependencyTypeAllowedValues.contains(value)) {
747740
throw FormatException(
748-
'Expected one of ${_dependencyTypes.join(', ')} but got `$value`.',
741+
'Expected one of ${dependencyTypeAllowedValues.join(', ')} '
742+
'but got `$value`.',
749743
);
750744
}
751745
}
@@ -757,7 +751,7 @@ List<String>? _dependencyType(Object? value) {
757751
/// Accepts only `text` or `csv`.
758752
String? _reporter(Object? value) {
759753
if (value == null) return null;
760-
if (value != 'text' && value != 'csv') {
754+
if (!reporterAllowedValues.contains(value)) {
761755
throw FormatException('Expected `text` or `csv` but got `$value`.');
762756
}
763757
return value as String;

test/src/commands/create/create_subcommand_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1091,7 +1091,8 @@ Run "runner help" to see global options.''';
10911091
isA<UsageException>().having(
10921092
(e) => e.message,
10931093
'message',
1094-
'"unknown" is not an allowed value for option "--template".',
1094+
'"unknown" is not an allowed value for the `create.template` '
1095+
'key in `$veryGoodConfigFileName`.',
10951096
),
10961097
),
10971098
);

test/src/very_good_config/very_good_config_test.dart

Lines changed: 45 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -497,14 +497,16 @@ packages:
497497
});
498498
});
499499

500-
group('loadFromClosestAncestor', () {
500+
group('load', () {
501501
late Directory tempDir;
502502
late Directory nestedDir;
503+
late Logger logger;
503504

504505
setUp(() {
505506
tempDir = Directory.systemTemp.createTempSync('very_good_config_');
506507
nestedDir = Directory(p.join(tempDir.path, 'packages', 'foo'))
507508
..createSync(recursive: true);
509+
logger = _MockLogger();
508510
});
509511

510512
tearDown(() {
@@ -513,24 +515,42 @@ packages:
513515
}
514516
});
515517

518+
test('returns the parsed config when very_good.yaml is valid', () {
519+
File(p.join(tempDir.path, veryGoodConfigFileName)).writeAsStringSync('''
520+
packages:
521+
get:
522+
recursive: true
523+
''');
524+
525+
final config = VeryGoodConfig.load(tempDir, logger: logger);
526+
527+
expect(config, isNotNull);
528+
expect(config!.packages.get.recursive, isTrue);
529+
verifyNever(() => logger.err(any()));
530+
});
531+
516532
test('reads config from the starting directory', () {
517533
File(p.join(nestedDir.path, veryGoodConfigFileName)).writeAsStringSync(
518534
'''
519535
test:
520536
min_coverage: 80
521537
''',
522538
);
523-
final config = VeryGoodConfig.loadFromClosestAncestor(nestedDir);
524-
expect(config.test.minCoverage, equals('80'));
539+
540+
final config = VeryGoodConfig.load(nestedDir, logger: logger);
541+
542+
expect(config?.test.minCoverage, equals('80'));
525543
});
526544

527545
test('reads config from an ancestor directory', () {
528546
File(p.join(tempDir.path, veryGoodConfigFileName)).writeAsStringSync('''
529547
test:
530548
min_coverage: 90
531549
''');
532-
final config = VeryGoodConfig.loadFromClosestAncestor(nestedDir);
533-
expect(config.test.minCoverage, equals('90'));
550+
551+
final config = VeryGoodConfig.load(nestedDir, logger: logger);
552+
553+
expect(config?.test.minCoverage, equals('90'));
534554
});
535555

536556
test('prefers the closest config over an ancestor', () {
@@ -544,55 +564,10 @@ test:
544564
min_coverage: 80
545565
''',
546566
);
547-
final config = VeryGoodConfig.loadFromClosestAncestor(nestedDir);
548-
expect(config.test.minCoverage, equals('80'));
549-
});
550567

551-
test('returns empty config when no file is found in any ancestor', () {
552-
expect(
553-
VeryGoodConfig.loadFromClosestAncestor(nestedDir),
554-
equals(VeryGoodConfig.empty),
555-
);
556-
});
568+
final config = VeryGoodConfig.load(nestedDir, logger: logger);
557569

558-
test('rethrows parse exception when the closest file is malformed', () {
559-
File(
560-
p.join(nestedDir.path, veryGoodConfigFileName),
561-
).writeAsStringSync('- not\n- a\n- map');
562-
expect(
563-
() => VeryGoodConfig.loadFromClosestAncestor(nestedDir),
564-
throwsA(isA<VeryGoodConfigParseException>()),
565-
);
566-
});
567-
});
568-
569-
group('load', () {
570-
late Directory tempDir;
571-
late Logger logger;
572-
573-
setUp(() {
574-
tempDir = Directory.systemTemp.createTempSync('very_good_config_');
575-
logger = _MockLogger();
576-
});
577-
578-
tearDown(() {
579-
if (tempDir.existsSync()) {
580-
tempDir.deleteSync(recursive: true);
581-
}
582-
});
583-
584-
test('returns the parsed config when very_good.yaml is valid', () {
585-
File(p.join(tempDir.path, veryGoodConfigFileName)).writeAsStringSync('''
586-
packages:
587-
get:
588-
recursive: true
589-
''');
590-
591-
final config = VeryGoodConfig.load(tempDir, logger: logger);
592-
593-
expect(config, isNotNull);
594-
expect(config!.packages.get.recursive, isTrue);
595-
verifyNever(() => logger.err(any()));
570+
expect(config?.test.minCoverage, equals('80'));
596571
});
597572

598573
test('returns an empty config when no very_good.yaml exists', () {
@@ -619,6 +594,24 @@ packages:
619594
).called(1);
620595
},
621596
);
597+
598+
test(
599+
'logs an error and returns null when the closest file is malformed',
600+
() {
601+
File(
602+
p.join(nestedDir.path, veryGoodConfigFileName),
603+
).writeAsStringSync('- not\n- a\n- map');
604+
605+
final config = VeryGoodConfig.load(nestedDir, logger: logger);
606+
607+
expect(config, isNull);
608+
verify(
609+
() => logger.err(
610+
any(that: contains('Could not read `very_good.yaml`')),
611+
),
612+
).called(1);
613+
},
614+
);
622615
});
623616

624617
test('supports value equality', () {

0 commit comments

Comments
 (0)