From af84860fa6276b47d901657e2970df41ad801ee7 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 26 May 2026 15:57:10 -0700 Subject: [PATCH 1/5] [material_ui] Set up `gen_defaults` sub-directory (#11762) Work towards https://github.com/flutter/flutter/issues/186906 Adds the initial `gen_defaults` sub-directory with the initial `TokenTemplate` and generator. This is simply the initial scaffold, we will need to add additional helper functions as necessary as we begin adding the actual templates. - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] page, which explains my responsibilities. - [x] I read and followed the [relevant style guides] and ran [the auto-formatter]. - [x] I signed the [CLA]. - [x] The title of the PR starts with the name of the package surrounded by square brackets, e.g. `[shared_preferences]` - [x] I [linked to at least one issue that this PR fixes] in the description above. - [x] I followed [the version and CHANGELOG instructions], using [semantic versioning] and the [repository CHANGELOG style], or I have commented below to indicate which documented exception this PR falls under[^1]. - [x] I updated/added any relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or I have commented below to indicate which [test exemption] this PR falls under[^1]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. [Contributor Guide]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md [relevant style guides]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style [the auto-formatter]: https://github.com/flutter/packages/blob/main/script/tool/README.md#format-code [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [linked to at least one issue that this PR fixes]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview [the version and CHANGELOG instructions]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#version-and-changelog-updates [semantic versioning]: https://dart.dev/tools/pub/versioning#semantic-versions [repository CHANGELOG style]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog-style [test exemption]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests --- .../material_ui/tool/gen_defaults/README.md | 24 ++++ .../tool/gen_defaults/analysis_options.yaml | 4 + .../tool/gen_defaults/bin/gen_defaults.dart | 28 +++++ .../tool/gen_defaults/pubspec.yaml | 17 +++ .../tool/gen_defaults/templates/template.dart | 111 +++++++++++++++++ .../gen_defaults/test/gen_defaults_test.dart | 112 ++++++++++++++++++ .../test/test_fixtures/button_token_data.dart | 8 ++ .../test/test_fixtures/test_templates.dart | 66 +++++++++++ 8 files changed, 370 insertions(+) create mode 100644 packages/material_ui/tool/gen_defaults/README.md create mode 100644 packages/material_ui/tool/gen_defaults/analysis_options.yaml create mode 100644 packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart create mode 100644 packages/material_ui/tool/gen_defaults/pubspec.yaml create mode 100644 packages/material_ui/tool/gen_defaults/templates/template.dart create mode 100644 packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart create mode 100644 packages/material_ui/tool/gen_defaults/test/test_fixtures/button_token_data.dart create mode 100644 packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart diff --git a/packages/material_ui/tool/gen_defaults/README.md b/packages/material_ui/tool/gen_defaults/README.md new file mode 100644 index 000000000000..56581126a8ca --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/README.md @@ -0,0 +1,24 @@ +## Token Defaults Generator + +Script that generates component theme data defaults based on token data. + +## Usage +Run this program from the root of the git repository: +```sh +dart packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart [-v] +``` + +This updates generated component theming files under +`packages/material_ui/lib/src/generated`. + +## Templates + +There is a template file for every component that needs defaults from the token +database. These templates are implemented as subclasses of either `M3TokenTemplate` or `M3ETokenTemplate`. + +Templates need to override the `generateContents` method to provide the +generated code block as a string. + +## Tokens + +Tokens are stored in `data/`, and are sourced from an internal Google database. \ No newline at end of file diff --git a/packages/material_ui/tool/gen_defaults/analysis_options.yaml b/packages/material_ui/tool/gen_defaults/analysis_options.yaml new file mode 100644 index 000000000000..1fb98de01a0b --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/analysis_options.yaml @@ -0,0 +1,4 @@ +include: ../../../../analysis_options.yaml + +formatter: + page_width: 100 diff --git a/packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart b/packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart new file mode 100644 index 000000000000..2da295bc4e99 --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// ## Usage +// +// Run from the root of flutter/packages: +// +// ``` +// dart packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart [-v] +// ``` + +import 'package:args/args.dart'; + +// TODO(elliette): Import template files. +// import '../templates/x_template.dart'; + +Future main(List args) async { + // Parse arguments + final parser = ArgParser(); + parser.addFlag('verbose', abbr: 'v', help: 'Enable verbose output', negatable: false); + final ArgResults argResults = parser.parse(args); + // TODO(elliette): Add token logger when verbose flag is used. + // ignore: unused_local_variable + final verbose = argResults['verbose'] as bool; + // TODO(elliette): Invoke template generators. + // const XTemplate().generateFile(verbose: verbose); +} diff --git a/packages/material_ui/tool/gen_defaults/pubspec.yaml b/packages/material_ui/tool/gen_defaults/pubspec.yaml new file mode 100644 index 000000000000..6ab022d87c3d --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/pubspec.yaml @@ -0,0 +1,17 @@ +name: gen_defaults +description: A command line script to generate Material component defaults from the token database. +publish_to: none +version: 1.0.0 + +environment: + sdk: ^3.10.0-0 + +resolution: workspace + +dependencies: + args: any + meta: any + +dev_dependencies: + path: any + test: any diff --git a/packages/material_ui/tool/gen_defaults/templates/template.dart b/packages/material_ui/tool/gen_defaults/templates/template.dart new file mode 100644 index 000000000000..e10ffca26693 --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/templates/template.dart @@ -0,0 +1,111 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:meta/meta.dart'; + +enum _MaterialVersion { material3, material3Expressive } + +abstract class M3TokenTemplate extends _TokenTemplate { + const M3TokenTemplate(); + + @override + _MaterialVersion get _version => _MaterialVersion.material3; +} + +abstract class M3ETokenTemplate extends _TokenTemplate { + const M3ETokenTemplate(); + + @override + _MaterialVersion get _version => _MaterialVersion.material3Expressive; +} + +abstract class _TokenTemplate { + const _TokenTemplate(); + + static const String copyrightHeader = ''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +'''; + + static const String headerComment = ''' + +// Do not edit by hand. The code is generated from data in the Material +// Design token database by the script: +// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart. +'''; + + /// The Material version this template is for. + _MaterialVersion get _version; + + /// The name of the template, which corresponds to the target file name. + /// E.g., 'typography' for generating 'typography_defaults.g.dart'. + String get name; + + @visibleForTesting + String get materialLib { + const packagePath = 'packages/material_ui'; + const generatedDirectory = 'lib/src/generated'; + final String relativeOutputPath = switch (_version) { + _MaterialVersion.material3 => generatedDirectory, + _MaterialVersion.material3Expressive => '$generatedDirectory/material_3_expressive', + }; + if (Directory(packagePath).existsSync()) { + return '$packagePath/$relativeOutputPath'; + } + return relativeOutputPath; + } + + String generateContents(); + + void generateFile({bool verbose = false}) { + final fileName = '$materialLib/${name}_defaults.g.dart'; + if (verbose) { + stdout.writeln('Generating file: $fileName'); + stdout.writeln('Target parent file name: $name.dart'); + } + final file = File(fileName); + if (!file.existsSync()) { + if (verbose) { + stdout.writeln('File does not exist, creating it.'); + } + file.createSync(recursive: true); + } + + final parentName = '$name.dart'; + + if (verbose) { + stdout.writeln('Generating contents...'); + } + final buffer = StringBuffer(); + buffer.write(copyrightHeader); + buffer.write(headerComment); + final String partOfPath = switch (_version) { + _MaterialVersion.material3 => '../$parentName', + _MaterialVersion.material3Expressive => '../../material_3_expressive/$parentName', + }; + buffer.write("part of '$partOfPath';\n\n"); + buffer.write(generateContents()); + + if (verbose) { + stdout.writeln('Writing generated contents to $fileName...'); + } + file.writeAsStringSync(buffer.toString()); + if (verbose) { + stdout.writeln('Formatting $fileName...'); + } + final ProcessResult result = Process.runSync(Platform.resolvedExecutable, [ + 'format', + fileName, + ]); + if (result.exitCode != 0) { + stderr.writeln('Failed to format $fileName: ${result.stderr}'); + } + if (verbose) { + stdout.writeln('Done generating $fileName.'); + } + } +} diff --git a/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart new file mode 100644 index 000000000000..91053fcd959d --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart @@ -0,0 +1,112 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:test/test.dart'; +import 'test_fixtures/test_templates.dart'; + +void main() { + Directory? tempDir; + String testPath() => tempDir!.path; + + group('gen_defaults templates', () { + setUp(() { + tempDir = Directory.systemTemp.createTempSync('gen_defaults'); + }); + + tearDown(() { + tempDir!.deleteSync(recursive: true); + }); + + test('will generate a part file ending in _defaults.g.dart', () { + final template = ButtonTemplate(testPath()); + template.generateFile(verbose: true); + + final file = File('${testPath()}/button_defaults.g.dart'); + expect(file.existsSync(), isTrue); + }); + + test('will generate a file with the correct header text', () { + final template = ButtonTemplate(testPath()); + template.generateFile(); + + final file = File('${testPath()}/button_defaults.g.dart'); + final String fileContents = file.readAsStringSync(); + expect(fileContents, contains(_fileHeader)); + }); + + test('will generate a file with the expected contents', () { + final template = ButtonTemplate(testPath()); + template.generateFile(); + + final file = File('${testPath()}/button_defaults.g.dart'); + final String fileContents = file.readAsStringSync(); + expect(fileContents, contains(_buttonDefaultsClass)); + }); + + test('will completely overwrite any previous code', () { + final file = File('${testPath()}/button_defaults.g.dart'); + const randomText = 'Pre-existing random text.'; + file.writeAsStringSync(randomText); + + final template = ButtonTemplate(testPath()); + template.generateFile(); + final String fileContents = file.readAsStringSync(); + expect(fileContents, isNot(contains(randomText))); + expect(fileContents, contains(_buttonDefaultsClass)); + }); + + test('will run dart format over the generated file', () { + final template = UnformattedTemplate(testPath()); + template.generateFile(); + + final file = File('${testPath()}/unformatted_defaults.g.dart'); + expect(file.readAsStringSync(), contains(formattedClass)); + }); + + test('materialLib path resolves correctly based on MaterialVersion', () { + final m3Template = TestM3Template(); + final m3ExpressiveTemplate = TestM3ExpressiveTemplate(); + const materialUiDir = 'packages/material_ui'; + const generatedDir = 'lib/src/generated'; + + final bool hasPackageDir = Directory(materialUiDir).existsSync(); + if (hasPackageDir) { + expect(m3Template.materialLib, '$materialUiDir/$generatedDir'); + expect( + m3ExpressiveTemplate.materialLib, + '$materialUiDir/$generatedDir/material_3_expressive', + ); + } else { + expect(m3Template.materialLib, generatedDir); + expect(m3ExpressiveTemplate.materialLib, '$generatedDir/material_3_expressive'); + } + }); + }); +} + +const _fileHeader = ''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Do not edit by hand. The code is generated from data in the Material +// Design token database by the script: +// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart. +'''; + +const _buttonDefaultsClass = ''' +class _ButtonDefaults { + static const double height = 40.0; + static const double borderRadius = 8.0; +} +'''; + +const formattedClass = ''' +class UnformattedClass { + final int x = 1; + final String y = 'hello'; +} +'''; diff --git a/packages/material_ui/tool/gen_defaults/test/test_fixtures/button_token_data.dart b/packages/material_ui/tool/gen_defaults/test/test_fixtures/button_token_data.dart new file mode 100644 index 000000000000..dbebb961a1e7 --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/test/test_fixtures/button_token_data.dart @@ -0,0 +1,8 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +class TokenButton { + static const double height = 40.0; + static const double borderRadius = 8.0; +} diff --git a/packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart b/packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart new file mode 100644 index 000000000000..e56d0c8ef85d --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart @@ -0,0 +1,66 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../../templates/template.dart'; +import 'button_token_data.dart'; + +class ButtonTemplate extends M3ETokenTemplate { + ButtonTemplate(this.customMaterialLib); + + final String customMaterialLib; + + @override + String get name => 'button'; + + @override + String get materialLib => customMaterialLib; + + @override + String generateContents() { + return ''' +class _ButtonDefaults { + static const double height = ${TokenButton.height}; + static const double borderRadius = ${TokenButton.borderRadius}; +} +'''; + } +} + +class UnformattedTemplate extends M3TokenTemplate { + UnformattedTemplate(this.customMaterialLib); + + final String customMaterialLib; + + @override + String get name => 'unformatted'; + + @override + String get materialLib => customMaterialLib; + + @override + String generateContents() { + return ''' +class UnformattedClass { +final int x = 1 ; + final String y = 'hello' ; +} +'''; + } +} + +class TestM3Template extends M3TokenTemplate { + @override + String get name => 'm3'; + + @override + String generateContents() => ''; +} + +class TestM3ExpressiveTemplate extends M3ETokenTemplate { + @override + String get name => 'm3e'; + + @override + String generateContents() => ''; +} From 2c56e3e89e0d1e9687f2417614c799c2131b2586 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:47:23 -0700 Subject: [PATCH 2/5] Add CHANGELOG entry --- .../pending_changelogs/change_2026_08_13_1786652261816.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/material_ui/pending_changelogs/change_2026_08_13_1786652261816.yaml diff --git a/packages/material_ui/pending_changelogs/change_2026_08_13_1786652261816.yaml b/packages/material_ui/pending_changelogs/change_2026_08_13_1786652261816.yaml new file mode 100644 index 000000000000..20995a7f1cb2 --- /dev/null +++ b/packages/material_ui/pending_changelogs/change_2026_08_13_1786652261816.yaml @@ -0,0 +1,3 @@ +changelog: | + - Set-up tool/gen_defaults sub-directory. +version: skip From 3a518be73098c7dae2dba5acaf76a345e7637e62 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:03:56 -0700 Subject: [PATCH 3/5] Remove workspace resolution --- packages/material_ui/tool/gen_defaults/pubspec.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/material_ui/tool/gen_defaults/pubspec.yaml b/packages/material_ui/tool/gen_defaults/pubspec.yaml index 6ab022d87c3d..13b1d0fb6d86 100644 --- a/packages/material_ui/tool/gen_defaults/pubspec.yaml +++ b/packages/material_ui/tool/gen_defaults/pubspec.yaml @@ -6,8 +6,6 @@ version: 1.0.0 environment: sdk: ^3.10.0-0 -resolution: workspace - dependencies: args: any meta: any From c78876221991e6add8ae742c94cf9fca68b1c302 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:49:15 -0700 Subject: [PATCH 4/5] Bump SDK --- packages/material_ui/tool/gen_defaults/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/material_ui/tool/gen_defaults/pubspec.yaml b/packages/material_ui/tool/gen_defaults/pubspec.yaml index 13b1d0fb6d86..eefb89500ac4 100644 --- a/packages/material_ui/tool/gen_defaults/pubspec.yaml +++ b/packages/material_ui/tool/gen_defaults/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none version: 1.0.0 environment: - sdk: ^3.10.0-0 + sdk: ^3.12.0 dependencies: args: any From ebedb7f69fcb8d2dfc99d8445f684be31477a708 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:00:44 -0700 Subject: [PATCH 5/5] Fix dashboard checks --- packages/material_ui/tool/gen_defaults/pubspec.yaml | 8 ++++---- .../tool/gen_defaults/test/gen_defaults_test.dart | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/material_ui/tool/gen_defaults/pubspec.yaml b/packages/material_ui/tool/gen_defaults/pubspec.yaml index eefb89500ac4..d591cbe44453 100644 --- a/packages/material_ui/tool/gen_defaults/pubspec.yaml +++ b/packages/material_ui/tool/gen_defaults/pubspec.yaml @@ -7,9 +7,9 @@ environment: sdk: ^3.12.0 dependencies: - args: any - meta: any + args: ^2.7.0 + meta: ^1.18.0 + path: ^1.9.1 dev_dependencies: - path: any - test: any + test: ^1.31.1 diff --git a/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart index 91053fcd959d..c4830ae6fda6 100644 --- a/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart +++ b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +@TestOn('!chrome') +library; + import 'dart:io'; import 'package:test/test.dart';