diff --git a/packages/flutter_tools/lib/src/commands/create_base.dart b/packages/flutter_tools/lib/src/commands/create_base.dart index b0de202c94..4f801acc9b 100644 --- a/packages/flutter_tools/lib/src/commands/create_base.dart +++ b/packages/flutter_tools/lib/src/commands/create_base.dart @@ -389,7 +389,7 @@ abstract class CreateBase extends FlutterCommand { 'macosIdentifier': appleIdentifier, 'linuxIdentifier': linuxIdentifier, 'windowsIdentifier': windowsIdentifier, - 'description': projectDescription != null ? escapeYamlString(projectDescription) : null, + 'description': projectDescription, 'dartSdk': '$flutterRoot/bin/cache/dart-sdk', 'androidMinApiLevel': android_common.minApiLevel, 'androidSdkVersion': kAndroidSdkMinVersion, diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index b86046ae94..a6bc9291ca 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart @@ -361,7 +361,13 @@ class Template { context['androidIdentifier'] = _escapeKotlinKeywords(androidIdentifier); } - final String renderedContents = _templateRenderer.renderString(templateContents, context); + // Use a copy of the context, + // since the original is used in rendering other templates. + final Map localContext = finalDestinationFile.path.endsWith('.yaml') + ? _createEscapedContextCopy(context) + : context; + + final String renderedContents = _templateRenderer.renderString(templateContents, localContext); finalDestinationFile.writeAsStringSync(renderedContents); @@ -377,6 +383,21 @@ class Template { } } +/// Create a copy of the given [context], escaping its values when necessary. +/// +/// Returns the copied context. +Map _createEscapedContextCopy(Map context) { + final Map localContext = Map.of(context); + + final String? description = localContext['description'] as String?; + + if (description != null && description.isNotEmpty) { + localContext['description'] = escapeYamlString(description); + } + + return localContext; +} + String _escapeKotlinKeywords(String androidIdentifier) { final List segments = androidIdentifier.split('.'); final List correctedSegments = segments.map( diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index 4e5488513f..3d5c238a3a 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -3384,6 +3384,32 @@ void main() { FeatureFlags: () => TestFeatureFlags(), Logger: () => logger, }); + + testUsingContext('Does not double quote description in index.html on web', () async { + await _createProject( + projectDir, + ['--no-pub', '--platforms=web'], + ['pubspec.yaml', 'web/index.html'], + ); + + final String rawIndexHtml = await projectDir.childDirectory('web').childFile('index.html').readAsString(); + const String expectedDescription = ''; + + expect(rawIndexHtml.contains(expectedDescription), isTrue); + }); + + testUsingContext('Does not double quote description in manifest.json on web', () async { + await _createProject( + projectDir, + ['--no-pub', '--platforms=web'], + ['pubspec.yaml', 'web/manifest.json'], + ); + + final String rawManifestJson = await projectDir.childDirectory('web').childFile('manifest.json').readAsString(); + const String expectedDescription = '"description": "A new Flutter project."'; + + expect(rawManifestJson.contains(expectedDescription), isTrue); + }); } Future _createProject(