mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 14:47:22 +08:00
[tools] Validate pubspec topic format (#5565)
Fixes https://github.com/flutter/flutter/issues/139305
This commit is contained in:
@ -332,7 +332,7 @@ class PubspecCheckCommand extends PackageLoopingCommand {
|
||||
false;
|
||||
}
|
||||
|
||||
// Validates the "implements" keyword for a plugin, returning an error
|
||||
// Validates the "topics" keyword for a plugin, returning an error
|
||||
// string if there are any issues.
|
||||
String? _checkTopics(
|
||||
Pubspec pubspec, {
|
||||
@ -343,6 +343,10 @@ class PubspecCheckCommand extends PackageLoopingCommand {
|
||||
return 'A published package should include "topics". '
|
||||
'See https://dart.dev/tools/pub/pubspec#topics.';
|
||||
}
|
||||
if (topics.length > 5) {
|
||||
return 'A published package should have maximum 5 topics. '
|
||||
'See https://dart.dev/tools/pub/pubspec#topics.';
|
||||
}
|
||||
if (isFlutterPlugin(package) && package.isFederated) {
|
||||
final String pluginName = package.directory.parent.basename;
|
||||
// '_' isn't allowed in topics, so convert to '-'.
|
||||
@ -352,6 +356,19 @@ class PubspecCheckCommand extends PackageLoopingCommand {
|
||||
'a topic. Add "$topicName" to the "topics" section.';
|
||||
}
|
||||
}
|
||||
|
||||
// Validates topic names according to https://dart.dev/tools/pub/pubspec#topics
|
||||
final RegExp expectedTopicFormat = RegExp(r'^[a-z](?:-?[a-z0-9]+)*$');
|
||||
final Iterable<String> invalidTopics = topics.where((String topic) =>
|
||||
!expectedTopicFormat.hasMatch(topic) ||
|
||||
topic.length < 2 ||
|
||||
topic.length > 32);
|
||||
if (invalidTopics.isNotEmpty) {
|
||||
return 'Invalid topic(s): ${invalidTopics.join(', ')} in "topics" section. '
|
||||
'Topics must consist of lowercase alphanumerical characters or dash (but no double dash), '
|
||||
'start with a-z and ending with a-z or 0-9, have a minimum of 2 characters '
|
||||
'and have a maximum of 32 characters.';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -633,6 +633,270 @@ ${_topicsSection()}
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when topic name contains a space', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['plugin a'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Invalid topic(s): plugin a in "topics" section. '),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when topic a topic name contains double dash', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['plugin--a'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Invalid topic(s): plugin--a in "topics" section. '),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when topic a topic name starts with a number', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['1plugin-a'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Invalid topic(s): 1plugin-a in "topics" section. '),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when topic a topic name contains uppercase', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['plugin-A'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Invalid topic(s): plugin-A in "topics" section. '),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when there are more than 5 topics', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>[
|
||||
'plugin-a',
|
||||
'plugin-a',
|
||||
'plugin-a',
|
||||
'plugin-a',
|
||||
'plugin-a',
|
||||
'plugin-a'
|
||||
])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains(
|
||||
' A published package should have maximum 5 topics. See https://dart.dev/tools/pub/pubspec#topics.'),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails if a topic name is longer than 32 characters', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['foobarfoobarfoobarfoobarfoobarfoobarfoo'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains(
|
||||
'Invalid topic(s): foobarfoobarfoobarfoobarfoobarfoobarfoo in "topics" section. '),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails if a topic name is longer than 2 characters', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['a'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Invalid topic(s): a in "topics" section. '),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails if a topic name ends in a dash', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['plugin-'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Invalid topic(s): plugin- in "topics" section. '),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('Invalid topics section has expected error message', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
||||
plugin.pubspecFile.writeAsStringSync('''
|
||||
${_headerSection('plugin')}
|
||||
${_environmentSection()}
|
||||
${_flutterSection(isPlugin: true)}
|
||||
${_dependenciesSection()}
|
||||
${_devDependenciesSection()}
|
||||
${_topicsSection(<String>['plugin-A', 'Plugin-b'])}
|
||||
''');
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['pubspec-check'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Invalid topic(s): plugin-A, Plugin-b in "topics" section. '
|
||||
'Topics must consist of lowercase alphanumerical characters or dash (but no double dash), '
|
||||
'start with a-z and ending with a-z or 0-9, have a minimum of 2 characters '
|
||||
'and have a maximum of 32 characters.'),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when environment section is out of order', () async {
|
||||
final RepositoryPackage plugin =
|
||||
createFakePlugin('plugin', packagesDir, examples: <String>[]);
|
||||
|
Reference in New Issue
Block a user