diff --git a/lib/src/validator.dart b/lib/src/validator.dart index 4336d4a08..5a03d7b71 100644 --- a/lib/src/validator.dart +++ b/lib/src/validator.dart @@ -19,6 +19,7 @@ import 'validator/dependency_override.dart'; import 'validator/deprecated_fields.dart'; import 'validator/directory.dart'; import 'validator/executable.dart'; +import 'validator/file_case.dart'; import 'validator/flutter_constraint.dart'; import 'validator/flutter_plugin_format.dart'; import 'validator/gitignore.dart'; @@ -136,6 +137,7 @@ abstract class Validator { required List errors, }) async { var validators = [ + FileCaseValidator(), AnalyzeValidator(), GitignoreValidator(), PubspecValidator(), diff --git a/lib/src/validator/file_case.dart b/lib/src/validator/file_case.dart new file mode 100644 index 000000000..ad73d47e7 --- /dev/null +++ b/lib/src/validator/file_case.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2023, 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:collection/collection.dart'; + +import '../validator.dart'; + +/// Validates that a package files all are unique even after case-normalization. +class FileCaseValidator extends Validator { + @override + Future validate() async { + final lowerCaseToFile = {}; + for (final file in files.sorted()) { + final lowerCase = file.toLowerCase(); + final existing = lowerCaseToFile[lowerCase]; + if (existing != null) { + errors.add(''' +The file $file and $existing only differ in capitalization. + +This is not supported across platforms. + +Try renaming one of them. +'''); + break; + } + lowerCaseToFile[lowerCase] = file; + } + } +} diff --git a/test/validator/file_case_test.dart b/test/validator/file_case_test.dart new file mode 100644 index 000000000..67d820039 --- /dev/null +++ b/test/validator/file_case_test.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2023, 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. + +// These tests only work on case-sensitive file systems (ie. only on linux). +@OnPlatform({ + 'windows': Skip('Windows file system is case-insensitive'), + 'mac-os': Skip('MacOS file system is case-insensitive') +}) + +import 'package:pub/src/exit_codes.dart'; +import 'package:test/test.dart'; + +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +Future expectValidation(error, int exitCode) async { + await runPub( + error: error, + args: ['publish', '--dry-run'], + workingDirectory: d.path(appPath), + exitCode: exitCode, + ); +} + +late d.DirectoryDescriptor fakeFlutterRoot; + +void main() { + test('Recognizes files that only differ in capitalization.', () async { + await d.validPackage.create(); + await d.dir(appPath, [d.file('Pubspec.yaml')]).create(); + await expectValidation( + allOf( + contains('Package validation found the following error:'), + contains( + 'The file ./pubspec.yaml and ./Pubspec.yaml only differ in capitalization.', + ), + ), + DATA, + ); + }); +}