From 3dde5289995f630f3132dc89ea495cac5bff7305 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 23 Feb 2026 18:03:29 -0700 Subject: [PATCH 01/43] Add system props to sx migrator --- bin/mui_system_props_migration.dart | 15 + .../mui_system_props_migration.dart | 63 ++ .../system_props_to_sx_migrator.dart | 360 ++++++++++++ pubspec.yaml | 1 + .../system_props_to_sx_migrator_test.dart | 543 ++++++++++++++++++ test/test_fixtures/wsd_project/pubspec.yaml | 6 + 6 files changed, 988 insertions(+) create mode 100644 bin/mui_system_props_migration.dart create mode 100644 lib/src/executables/mui_system_props_migration.dart create mode 100644 lib/src/mui_suggestors/system_props_to_sx_migrator.dart create mode 100644 test/mui_suggestors/system_props_to_sx_migrator_test.dart diff --git a/bin/mui_system_props_migration.dart b/bin/mui_system_props_migration.dart new file mode 100644 index 00000000..7529f624 --- /dev/null +++ b/bin/mui_system_props_migration.dart @@ -0,0 +1,15 @@ +// Copyright 2026 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export 'package:over_react_codemod/src/executables/mui_system_props_migration.dart'; diff --git a/lib/src/executables/mui_system_props_migration.dart b/lib/src/executables/mui_system_props_migration.dart new file mode 100644 index 00000000..b55a3dd0 --- /dev/null +++ b/lib/src/executables/mui_system_props_migration.dart @@ -0,0 +1,63 @@ +// Copyright 2026 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:codemod/codemod.dart'; +import 'package:over_react_codemod/src/ignoreable.dart'; +import 'package:over_react_codemod/src/mui_suggestors/system_props_to_sx_migrator.dart'; +import 'package:over_react_codemod/src/util.dart'; + +const _changesRequiredOutput = """ + To update your code, run the following commands in your repository: + dart pub global activate over_react_codemod + dart pub global run over_react_codemod:mui_system_props_migration +"""; + +/// Migrates MUI system props to the `sx` prop. +/// +/// MUI System props (such as `mt={*}`, `bgcolor={*}`, and more) have been deprecated +/// in MUI v6 in favor of the `sx` prop. +/// +/// This codemod moves all System props to the sx prop, ensuring that existing +/// sx prop values are preserved and merged correctly. +void main(List args) async { + final parser = ArgParser.allowAnything(); + + final parsedArgs = parser.parse(args); + + // Work around allowAnything not allowing you to pass flags. + if (parsedArgs.arguments.contains('--help')) { + // Print command description; flags and other output will get printed via runInteractiveCodemod. + print('Migrates MUI system props to the `sx` prop.\n'); + print( + 'MUI System props (such as mt={*}, bgcolor={*}, and more) have been deprecated'); + print('in MUI v6 in favor of the sx prop.\n'); + print( + 'This codemod moves all System props to the sx prop, ensuring that existing'); + print('sx prop values are preserved and merged correctly.\n'); + } + + exitCode = await runInteractiveCodemod( + allDartPathsExceptHidden(), + aggregate([ + SystemPropsToSxMigrator(), + ].map((s) => ignoreable(s))), + defaultYes: true, + args: parsedArgs.rest, + additionalHelpOutput: parser.usage, + changesRequiredOutput: _changesRequiredOutput, + ); +} diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart new file mode 100644 index 00000000..edcd6964 --- /dev/null +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -0,0 +1,360 @@ +// Copyright 2026 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:collection/collection.dart'; +import 'package:over_react_codemod/src/util.dart'; +import 'package:over_react_codemod/src/util/component_usage.dart'; +import 'package:over_react_codemod/src/util/component_usage_migrator.dart'; +import 'package:over_react_codemod/src/util/element_type_helpers.dart'; + +/// A suggestor that migrates MUI system props to the `sx` prop. +/// +/// MUI System props (such as `mt={*}`, `bgcolor={*}`, and more) have been deprecated +/// in MUI v6 in favor of the `sx` prop. +/// +/// This migrator detects components that use system props and moves those props +/// to the `sx` prop, ensuring that existing `sx` prop values are preserved and merged correctly. +class SystemPropsToSxMigrator extends ComponentUsageMigrator { + @override + String get fixmePrefix => 'FIXME(mui_system_props_migration)'; + + // We'll handle conditionally migrating when we collect more data in migrateUsage. + @override + bool shouldMigrateUsage(FluentComponentUsage usage) => true; + + @override + get safeMethodCallNames { + return const { + // These are handled in getForwardedPropSources + 'addUnconsumedProps', + 'addAll', + 'addProps', + 'modifyProps', + // These are unrelated + 'addUnconsumedDomProps', + 'addAutomationId', + 'addTestId', + }; + } + + @override + get shouldFlagExtensionMembers => false; + + @override + get shouldFlagUntypedSingleProp => false; + + @override + get shouldFlagPrefixedProps => false; + + @override + get shouldFlagRefProp => false; + + @override + get shouldFlagClassName => false; + + @override + void migrateUsage(FluentComponentUsage usage) { + super.migrateUsage(usage); + + final deprecatedSystemProps = []; + PropAssignment? existingSxProp; + + // Identify system props and existing sx prop + for (final prop in usage.cascadedProps) { + final propName = prop.name.name; + if (propName == 'sx') { + existingSxProp = prop; + } else if (_systemPropNames.contains(propName)) { + final propElement = prop.staticElement?.nonSynthetic; + final enclosingElement = propElement?.enclosingElement; + late final componentName = + enclosingElement?.name?.replaceAll(RegExp(r'Props(Mixin)?$'), ''); + if (propElement != null && + propElement.hasDeprecated && + enclosingElement != null && + _componentsWithDeprecatedSystemProps.contains(componentName) && + enclosingElement.isDeclaredInPackage('unify_ui')) { + deprecatedSystemProps.add(prop); + } + } + } + + if (deprecatedSystemProps.isEmpty) return; + + // FIXME comments don't come with props + + final migratedSystemPropEntries = deprecatedSystemProps.map((prop) { + final propName = prop.name.name; + final propValue = context.sourceFile + .getText(prop.rightHandSide.offset, prop.rightHandSide.end); + return "'$propName': $propValue"; + }); + + // Remove all system props + for (final prop in deprecatedSystemProps) { + yieldRemovePropPatch(prop); + } + + bool shouldForceMultiline( + {required int elementCount, required int charCount}) { + return elementCount >= 3 || charCount >= 60; + } + + if (existingSxProp != null) { + // FIXME before vs after + final value = existingSxProp.rightHandSide; + if (value is SetOrMapLiteral && value.isMap) { + // Avoid inserting a double-comma if there's a trailing comma. + // While we're here, also preserve the trailing comma. + final hadTrailingComma = + value.rightBracket.previous?.type == TokenType.COMMA; + final maybePrecedingComma = + !hadTrailingComma && value.elements.isNotEmpty ? ', ' : ''; + final maybeTrailingComma = hadTrailingComma || + shouldForceMultiline( + elementCount: + value.elements.length + migratedSystemPropEntries.length, + charCount: value.toSource().length + + migratedSystemPropEntries.join(', ').length, + ) + ? ',' + : ''; + yieldPatch( + '$maybePrecedingComma${migratedSystemPropEntries.join(', ')}$maybeTrailingComma', + value.rightBracket.offset, + value.rightBracket.offset); + } else { + final type = value.staticType; + final nonNullable = type != null && + type is! DynamicType && + type.nullabilitySuffix == NullabilitySuffix.none; + yieldPatch('{...${nonNullable ? '' : '?'}', value.offset, value.offset); + yieldPatch( + ', ${migratedSystemPropEntries.join(', ')}}', value.end, value.end); + } + } else { + final forwardedPropSources = _getForwardedPropSources(usage); + final mightForwardSx = forwardedPropSources.any((source) { + final type = source?.staticType?.typeOrBound.tryCast(); + if (type != null && + type.isPropsClass && + type.element.name != 'UiProps') { + return type.element.lookUpGetter('sx', type.element.library) != null; + } + return true; + }); + + final String? additionalSxElement; + final bool needsFixme; + if (mightForwardSx) { + var canGetForwardedSx = false; + + final forwardedProps = forwardedPropSources.singleOrNull; + if (forwardedProps != null && + (forwardedProps is Identifier || + forwardedProps is PropertyAccess)) { + final forwardedPropsType = + forwardedProps.staticType?.typeOrBound.tryCast(); + if (forwardedPropsType?.element + .lookUpGetter('sx', forwardedPropsType.element.library) != + null) { + canGetForwardedSx = true; + } + } + + if (canGetForwardedSx) { + additionalSxElement = '...?${forwardedProps!.toSource()}.sx'; + needsFixme = false; + } else { + additionalSxElement = null; + needsFixme = true; + } + } else { + additionalSxElement = null; + needsFixme = false; + } + final fixme = needsFixme + ? '\n ' + + lineComment( + '$fixmePrefix - merge in any sx prop forwarded to this component, if needed') + : ''; + + final elements = [ + if (additionalSxElement != null) additionalSxElement, + ...migratedSystemPropEntries, + ]; + final maybeTrailingComma = shouldForceMultiline( + elementCount: elements.length, + charCount: elements.join(', ').length) + ? ',' + : ''; + + final insertionLocation = deprecatedSystemProps.last.node.end; + yieldPatch('$fixme..sx = {${elements.join(', ')}$maybeTrailingComma}', + insertionLocation, insertionLocation); + } + } +} + +/// Returns the set of expressions that are sources of props forwarded to the component in [usage], +/// or `null` for sources that were detected but don't cleanly map to a props expression. +Set _getForwardedPropSources(FluentComponentUsage usage) { + return usage.cascadedMethodInvocations.expand((c) { + final methodName = c.methodName.name; + late final arg = c.node.argumentList.arguments.firstOrNull; + + switch (methodName) { + case 'addUnconsumedProps': + return [null]; + case 'addAll': + case 'addProps': + if (arg is MethodInvocation && + (arg.methodName.name == 'getPropsToForward' || + arg.methodName.name == 'copyUnconsumedProps')) { + return [arg.realTarget]; + } + return [arg]; + case 'modifyProps': + if ((arg is MethodInvocation && + arg.methodName.name == 'addPropsToForward')) { + return [arg.realTarget]; + } + if (arg is Identifier && arg.name == 'addUnconsumedProps') { + return [null]; + } + return [null]; + default: + // Not a method that forwards props. + return []; + } + }).toSet(); +} + +const _componentsWithDeprecatedSystemProps = { + 'Box', + 'Container', + 'Grid', + 'Stack', + 'Typography', +}; + +const _systemPropNames = { + 'm', + 'mt', + 'mr', + 'mb', + 'ml', + 'mx', + 'my', + 'p', + 'pt', + 'pr', + 'pb', + 'pl', + 'px', + 'py', + 'width', + 'maxWidth', + 'minWidth', + 'height', + 'maxHeight', + 'minHeight', + 'boxSizing', + 'display', + 'displayPrint', + 'overflow', + 'textOverflow', + 'visibility', + 'whiteSpace', + 'flexBasis', + 'flexDirection', + 'flexWrap', + 'justifyContent', + 'alignItems', + 'alignContent', + 'order', + 'flex', + 'flexGrow', + 'flexShrink', + 'alignSelf', + 'justifyItems', + 'justifySelf', + 'gap', + 'columnGap', + 'rowGap', + 'gridColumn', + 'gridRow', + 'gridAutoFlow', + 'gridAutoColumns', + 'gridAutoRows', + 'gridTemplateColumns', + 'gridTemplateRows', + 'gridTemplateAreas', + 'gridArea', + 'bgcolor', + 'color', + 'zIndex', + 'position', + 'top', + 'right', + 'bottom', + 'left', + 'boxShadow', + 'border', + 'borderTop', + 'borderRight', + 'borderBottom', + 'borderLeft', + 'borderColor', + 'borderRadius', + 'fontFamily', + 'fontSize', + 'fontStyle', + 'fontWeight', + 'letterSpacing', + 'lineHeight', + 'textAlign', + 'textTransform', + 'margin', + 'marginTop', + 'marginRight', + 'marginBottom', + 'marginLeft', + 'marginX', + 'marginY', + 'marginInline', + 'marginInlineStart', + 'marginInlineEnd', + 'marginBlock', + 'marginBlockStart', + 'marginBlockEnd', + 'padding', + 'paddingTop', + 'paddingRight', + 'paddingBottom', + 'paddingLeft', + 'paddingX', + 'paddingY', + 'paddingInline', + 'paddingInlineStart', + 'paddingInlineEnd', + 'paddingBlock', + 'paddingBlockStart', + 'paddingBlockEnd', + 'typography', +}; diff --git a/pubspec.yaml b/pubspec.yaml index bd226b16..53c2c5df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ executables: null_safety_required_props: null_safety_prep: mui_migration: + mui_system_props_migration: required_flux_props: rmui_preparation: rmui_bundle_update: diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart new file mode 100644 index 00000000..da182012 --- /dev/null +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -0,0 +1,543 @@ +// Copyright 2026 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:over_react_codemod/src/mui_suggestors/system_props_to_sx_migrator.dart'; +import 'package:test/test.dart'; + +import '../resolved_file_context.dart'; +import '../util.dart'; + +void main() { + group('SystemPropsToSxMigrator', () { + final resolvedContext = SharedAnalysisContext.wsd; + setUpAll(resolvedContext.warmUpAnalysis); + + final testSuggestor = getSuggestorTester( + SystemPropsToSxMigrator(), + resolvedContext: resolvedContext, + ); + + test('migrates single system prop to sx', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box()..mt = 2)(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box()..sx = {'mt': 2})(); + } + '''), + ); + }); + + test('migrates multiple system props', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..mt = 2 + ..p = 3 + ..bgcolor = 'primary.main' + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = { + 'mt': 2, + 'p': 3, + 'bgcolor': 'primary.main', + } + )(); + } + '''), + ); + }); + + group('merges with existing sx map literal', () { + test('without trailing commas', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..sx = {'border': '1px solid black'} + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = {'border': '1px solid black', 'mt': 2} + )(); + } + '''), + ); + }); + + test('with trailing commas', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..sx = { + 'border': '1px solid black', + } + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = { + 'border': '1px solid black', + 'mt': 2, + } + )(); + } + '''), + ); + }); + + test('that is empty', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..sx = {} + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = {'mt': 2} + )(); + } + '''), + ); + }); + }); + + group('merges with forwarded sx prop using spread:', () { + test('nullable', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..sx = getSx() + ..mt = 2 + )(); + } + Map? getSx() => {'color': 'red'}; + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..sx = {...?getSx(), 'mt': 2} + )(); + } + Map? getSx() => {'color': 'red'}; + '''), + ); + }); + + test('non-nullable', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..sx = getSx() + ..mt = 2 + )(); + } + Map getSx() => {'color': 'red'}; + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..sx = {...getSx(), 'mt': 2} + )(); + } + Map getSx() => {'color': 'red'}; + '''), + ); + }); + + test('dynamic', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..sx = getSx() + ..mt = 2 + )(); + } + dynamic getSx() => {'color': 'red'}; + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..sx = {...?getSx(), 'mt': 2} + )(); + } + dynamic getSx() => {'color': 'red'}; + '''), + ); + }); + }); + + group('merges in sx from forwarded props', () { + test('from addProps', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..sx = {...?props.sx, 'mt': 2} + )(); + } + '''), + ); + }); + + test('from addAll', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addAll(props) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addAll(props) + ..sx = {...?props.sx, 'mt': 2} + )(); + } + '''), + ); + }); + + test('from getPropsToForward', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props.getPropsToForward()) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props.getPropsToForward()) + ..sx = {...?props.sx, 'mt': 2} + )(); + } + '''), + ); + }); + + test('from addPropsToForward', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..modifyProps(props.addPropsToForward()) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..modifyProps(props.addPropsToForward()) + ..sx = {...?props.sx, 'mt': 2} + )(); + } + '''), + ); + }); + + // Regression test + test('even when there are other unrelated calls in the cascade', + () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..addTestId('test-id') + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..addTestId('test-id') + ..sx = {...?props.sx, 'mt': 2} + )(); + } + '''), + ); + }); + }); + + group('adds FIXME comment when forwarding is ambiguous:', () { + group('generic props:', () { + test('Map', () async { + await testSuggestor( + input: withHeader(''' + content(Map props) { + (Box() + ..addAll(props) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(Map props) { + (Box() + ..addAll(props) + + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } + )(); + } + '''), + ); + }); + + test('UiProps', () async { + await testSuggestor( + input: withHeader(''' + content(UiProps props) { + (Box() + ..addAll(props) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(UiProps props) { + (Box() + ..addAll(props) + + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } + )(); + } + '''), + ); + }); + }); + + test('multiple prop forwarding calls', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props, BoxProps props2) { + (Box() + ..addProps(props) + ..addProps(props2) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props, BoxProps props2) { + (Box() + ..addProps(props) + ..addProps(props2) + + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } + )(); + } + '''), + ); + }); + }); + + test('handles complex prop values with expressions', () async { + await testSuggestor( + input: withHeader(''' + content(bool condition) { + (Box() + ..mt = condition ? 2 : 4 + ..p = getSpacing() + )(); + } + int getSpacing() => 3; + '''), + expectedOutput: withHeader(''' + content(bool condition) { + (Box() + ..sx = {'mt': condition ? 2 : 4, 'p': getSpacing()} + )(); + } + int getSpacing() => 3; + '''), + ); + }); + + test('handles responsive system prop values', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..mt = {'xs': 1, 'sm': 2, 'md': 3} + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = {'mt': {'xs': 1, 'sm': 2, 'md': 3}} + )(); + } + '''), + ); + }); + + test('preserves non-system props', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..id = 'test' + ..mt = 2 + ..className = 'custom-class' + ..onClick = (_) {} + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..id = 'test' + ..sx = {'mt': 2} + ..className = 'custom-class' + ..onClick = (_) {} + )(); + } + '''), + ); + }); + + test('handles multiple components in the same file', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box()..mt = 2)(); + (Box()..p = 3)(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box()..sx = {'mt': 2})(); + (Box()..sx = {'p': 3})(); + } + '''), + ); + }); + + test('respects orcm_ignore comments', () async { + await testSuggestor( + input: withHeader(''' + content() { + // orcm_ignore + (Box()..mt = 2)(); + } + '''), + ); + }); + + test('does not migrate components without deprecated system props', + () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..id = 'test' + ..className = 'test-class' + )(); + } + '''), + ); + }); + + test('does not migrate deprecated props with the same name as system props', + () async { + await testSuggestor( + input: withHeader(''' + content() { + (TextField() + ..color = '' + )(); + } + '''), + ); + }); + + test('does not flag unrelated cascades with FIXMEs', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..addProp('foo', 'bar') + ..['bar'] = 'baz' + ..ref = (_) {} + ..className = 'test-class' + )(); + } + '''), + ); + }); + }); +} + +String withHeader(String source) => ''' + //@dart=2.19 + + import 'package:over_react/over_react.dart'; + import 'package:unify_ui/unify_ui.dart'; + + $source +'''; diff --git a/test/test_fixtures/wsd_project/pubspec.yaml b/test/test_fixtures/wsd_project/pubspec.yaml index 3befe900..c7132afe 100644 --- a/test/test_fixtures/wsd_project/pubspec.yaml +++ b/test/test_fixtures/wsd_project/pubspec.yaml @@ -8,3 +8,9 @@ dependencies: name: web_skin_dart url: https://pub.workiva.org version: ^3.0.0 + unify_ui: + hosted: + name: unify_ui + url: https://pub.workiva.org + # First version system props were deprecated + version: ^2.20.5 From 80471e851f41603488581ef66955d5983da962df Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 23 Feb 2026 20:08:44 -0700 Subject: [PATCH 02/43] Take forwarded props location into account --- .../system_props_to_sx_migrator.dart | 87 ++++++++++++------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index edcd6964..8114e3ac 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:math'; + import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; @@ -150,7 +152,8 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { } else { final forwardedPropSources = _getForwardedPropSources(usage); final mightForwardSx = forwardedPropSources.any((source) { - final type = source?.staticType?.typeOrBound.tryCast(); + final type = source.propsExpression?.staticType?.typeOrBound + .tryCast(); if (type != null && type.isPropsClass && type.element.name != 'UiProps') { @@ -161,10 +164,11 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final String? additionalSxElement; final bool needsFixme; + + if (mightForwardSx) { var canGetForwardedSx = false; - - final forwardedProps = forwardedPropSources.singleOrNull; + final forwardedProps = forwardedPropSources.singleOrNull?.propsExpression; if (forwardedProps != null && (forwardedProps is Identifier || forwardedProps is PropertyAccess)) { @@ -204,45 +208,62 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { ? ',' : ''; - final insertionLocation = deprecatedSystemProps.last.node.end; + // Insert after any forwarded props to ensure sx isn't overwritten, + // and where the last system prop used to be to preserve the location and reduce diffs. + final insertionLocation = [ + ...forwardedPropSources.map((source) => source.cascadedMethod.node.end), + deprecatedSystemProps.last.node.end + ].reduce(max); + yieldPatch('$fixme..sx = {${elements.join(', ')}$maybeTrailingComma}', insertionLocation, insertionLocation); } } } +class _ForwardedPropSource { + final BuilderMethodInvocation cascadedMethod; + final Expression? propsExpression; + + _ForwardedPropSource(this.cascadedMethod, this.propsExpression); +} + /// Returns the set of expressions that are sources of props forwarded to the component in [usage], /// or `null` for sources that were detected but don't cleanly map to a props expression. -Set _getForwardedPropSources(FluentComponentUsage usage) { - return usage.cascadedMethodInvocations.expand((c) { - final methodName = c.methodName.name; - late final arg = c.node.argumentList.arguments.firstOrNull; - - switch (methodName) { - case 'addUnconsumedProps': - return [null]; - case 'addAll': - case 'addProps': - if (arg is MethodInvocation && - (arg.methodName.name == 'getPropsToForward' || - arg.methodName.name == 'copyUnconsumedProps')) { - return [arg.realTarget]; - } - return [arg]; - case 'modifyProps': - if ((arg is MethodInvocation && - arg.methodName.name == 'addPropsToForward')) { - return [arg.realTarget]; - } - if (arg is Identifier && arg.name == 'addUnconsumedProps') { - return [null]; +List<_ForwardedPropSource> _getForwardedPropSources( + FluentComponentUsage usage) { + return usage.cascadedMethodInvocations + .map((c) { + final methodName = c.methodName.name; + late final arg = c.node.argumentList.arguments.firstOrNull; + + switch (methodName) { + case 'addUnconsumedProps': + return _ForwardedPropSource(c, null); + case 'addAll': + case 'addProps': + if (arg is MethodInvocation && + (arg.methodName.name == 'getPropsToForward' || + arg.methodName.name == 'copyUnconsumedProps')) { + return _ForwardedPropSource(c, arg.realTarget); + } + return _ForwardedPropSource(c, arg); + case 'modifyProps': + if ((arg is MethodInvocation && + arg.methodName.name == 'addPropsToForward')) { + return _ForwardedPropSource(c, arg.realTarget); + } + if (arg is Identifier && arg.name == 'addUnconsumedProps') { + return _ForwardedPropSource(c, null); + } + return _ForwardedPropSource(c, null); + default: + // Not a method that forwards props. + return null; } - return [null]; - default: - // Not a method that forwards props. - return []; - } - }).toSet(); + }) + .whereNotNull() + .toList(); } const _componentsWithDeprecatedSystemProps = { From f06bcd653307ef8c598dc69d93752206914ada91 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 23 Feb 2026 20:09:24 -0700 Subject: [PATCH 03/43] Wrap longer 2-item sx --- .../mui_suggestors/system_props_to_sx_migrator.dart | 2 +- .../system_props_to_sx_migrator_test.dart | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 8114e3ac..3cafc413 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -114,7 +114,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { bool shouldForceMultiline( {required int elementCount, required int charCount}) { - return elementCount >= 3 || charCount >= 60; + return elementCount >= 3 || (elementCount > 1 && charCount >= 30); } if (existingSxProp != null) { diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index da182012..640b6218 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -82,7 +82,10 @@ void main() { expectedOutput: withHeader(''' content() { (Box() - ..sx = {'border': '1px solid black', 'mt': 2} + ..sx = { + 'border': '1px solid black', + 'mt': 2, + } )(); } '''), @@ -409,7 +412,10 @@ void main() { expectedOutput: withHeader(''' content(bool condition) { (Box() - ..sx = {'mt': condition ? 2 : 4, 'p': getSpacing()} + ..sx = { + 'mt': condition ? 2 : 4, + 'p': getSpacing(), + } )(); } int getSpacing() => 3; From f4a24a2d95a0713e4786bc514cd8bba113ac518b Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 23 Feb 2026 20:11:20 -0700 Subject: [PATCH 04/43] Fix addUnconsumedProps case --- lib/src/mui_suggestors/system_props_to_sx_migrator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 3cafc413..17acd0ec 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -239,7 +239,7 @@ List<_ForwardedPropSource> _getForwardedPropSources( switch (methodName) { case 'addUnconsumedProps': - return _ForwardedPropSource(c, null); + return _ForwardedPropSource(c, arg); case 'addAll': case 'addProps': if (arg is MethodInvocation && From cede441d64893807c987273192e13725e2ed4065 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 23 Feb 2026 20:19:52 -0700 Subject: [PATCH 05/43] Tweak trailing commas and multiline --- .../system_props_to_sx_migrator.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 17acd0ec..d82231aa 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -114,7 +114,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { bool shouldForceMultiline( {required int elementCount, required int charCount}) { - return elementCount >= 3 || (elementCount > 1 && charCount >= 30); + return elementCount >= 3 || (elementCount > 1 && charCount >= 20); } if (existingSxProp != null) { @@ -146,8 +146,15 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { type is! DynamicType && type.nullabilitySuffix == NullabilitySuffix.none; yieldPatch('{...${nonNullable ? '' : '?'}', value.offset, value.offset); + final maybeTrailingComma = shouldForceMultiline( + elementCount: migratedSystemPropEntries.length + 1, + charCount: [value.toSource(), ...migratedSystemPropEntries] + .join(', ') + .length) + ? ',' + : ''; yieldPatch( - ', ${migratedSystemPropEntries.join(', ')}}', value.end, value.end); + ', ${migratedSystemPropEntries.join(', ')}$maybeTrailingComma}', value.end, value.end); } } else { final forwardedPropSources = _getForwardedPropSources(usage); @@ -165,10 +172,10 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final String? additionalSxElement; final bool needsFixme; - if (mightForwardSx) { var canGetForwardedSx = false; - final forwardedProps = forwardedPropSources.singleOrNull?.propsExpression; + final forwardedProps = + forwardedPropSources.singleOrNull?.propsExpression; if (forwardedProps != null && (forwardedProps is Identifier || forwardedProps is PropertyAccess)) { From 8ce804d5cd6accbd393dcc43b6444179df930c4c Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 10:50:43 -0700 Subject: [PATCH 06/43] Add/update tests --- .../system_props_to_sx_migrator_test.dart | 495 +++++++++++++++--- 1 file changed, 432 insertions(+), 63 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 640b6218..2a4a1aa3 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -136,6 +136,33 @@ void main() { '''), ); }); + + test('with multiple existing entries', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..sx = { + 'backgroundColor': 'white', + 'color': 'blue', + } + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = { + 'backgroundColor': 'white', + 'color': 'blue', + 'mt': 2, + } + )(); + } + '''), + ); + }); }); group('merges with forwarded sx prop using spread:', () { @@ -206,13 +233,127 @@ void main() { }); }); + test('does not consider forwarding with an existing sx', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..sx = {'border': '1px solid black'} + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..sx = { + 'border': '1px solid black', + 'mt': 2, + } + )(); + } + '''), + ); + }); + group('merges in sx from forwarded props', () { - test('from addProps', () async { + group('forwarded with', () { + test('addProps', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + ..sx = {...?props.sx, 'mt': 2,} + )(); + } + '''), + ); + }); + + test('addAll', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addAll(props) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addAll(props) + ..sx = {...?props.sx, 'mt': 2,} + )(); + } + '''), + ); + }); + + test('getPropsToForward', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props.getPropsToForward()) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props.getPropsToForward()) + ..sx = {...?props.sx, 'mt': 2,} + )(); + } + '''), + ); + }); + + test('addPropsToForward', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..modifyProps(props.addPropsToForward()) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..modifyProps(props.addPropsToForward()) + ..sx = {...?props.sx, 'mt': 2,} + )(); + } + '''), + ); + }); + }); + + // Regression test + test('even when there are other unrelated calls in the cascade', + () async { await testSuggestor( input: withHeader(''' content(BoxProps props) { (Box() ..addProps(props) + ..addTestId('test-id') ..mt = 2 )(); } @@ -221,19 +362,22 @@ void main() { content(BoxProps props) { (Box() ..addProps(props) - ..sx = {...?props.sx, 'mt': 2} + ..addTestId('test-id') + ..sx = {...?props.sx, 'mt': 2,} )(); } '''), ); }); + }); - test('from addAll', () async { + group('adds FIXME comment when forwarding is ambiguous:', () { + test('modifyProps with unknown function', () async { await testSuggestor( input: withHeader(''' content(BoxProps props) { (Box() - ..addAll(props) + ..modifyProps((_) {}) ..mt = 2 )(); } @@ -241,83 +385,47 @@ void main() { expectedOutput: withHeader(''' content(BoxProps props) { (Box() - ..addAll(props) - ..sx = {...?props.sx, 'mt': 2} - )(); - } - '''), - ); - }); + ..modifyProps((_) {}) - test('from getPropsToForward', () async { - await testSuggestor( - input: withHeader(''' - content(BoxProps props) { - (Box() - ..addProps(props.getPropsToForward()) - ..mt = 2 - )(); - } - '''), - expectedOutput: withHeader(''' - content(BoxProps props) { - (Box() - ..addProps(props.getPropsToForward()) - ..sx = {...?props.sx, 'mt': 2} + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } )(); } '''), ); }); - test('from addPropsToForward', () async { + test('copyUnconsumedProps', () async { await testSuggestor( input: withHeader(''' - content(BoxProps props) { - (Box() - ..modifyProps(props.addPropsToForward()) - ..mt = 2 - )(); + abstract class FooComponent extends UiComponent2 { + content(BoxProps props) { + (Box() + ..addProps(copyUnconsumedProps()) + ..mt = 2 + )(); + } } '''), expectedOutput: withHeader(''' - content(BoxProps props) { - (Box() - ..modifyProps(props.addPropsToForward()) - ..sx = {...?props.sx, 'mt': 2} - )(); - } - '''), - ); - }); + abstract class FooComponent extends UiComponent2 { + content(BoxProps props) { + (Box() + ..addProps(copyUnconsumedProps()) - // Regression test - test('even when there are other unrelated calls in the cascade', - () async { - await testSuggestor( - input: withHeader(''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..addTestId('test-id') - ..mt = 2 - )(); - } - '''), - expectedOutput: withHeader(''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..addTestId('test-id') - ..sx = {...?props.sx, 'mt': 2} - )(); + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } + )(); + } } '''), ); }); - }); - group('adds FIXME comment when forwarding is ambiguous:', () { group('generic props:', () { test('Map', () async { await testSuggestor( @@ -368,6 +476,31 @@ void main() { '''), ); }); + + test('dynamic', () async { + await testSuggestor( + input: withHeader(''' + content(dynamic props) { + (Box() + ..addAll(props) + ..mt = 2 + )(); + } + '''), + expectedOutput: withHeader(''' + content(dynamic props) { + (Box() + ..addAll(props) + + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } + )(); + } + '''), + ); + }); }); test('multiple prop forwarding calls', () async { @@ -536,6 +669,242 @@ void main() { '''), ); }); + + + + group('insertion location:', () { + test('inserts after prop forwarding to avoid being overwritten, with a comment', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..mt = 2 + ..addProps(props) + ..id = 'test' + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + + // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering + ..sx = {...?props.sx, 'mt': 2,} + ..id = 'test' + )(); + } + '''), + ); + }); + + test('inserts at location of last system prop when no prop forwarding', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..id = 'first' + ..mt = 2 + ..className = 'middle' + ..p = 3 + ..onClick = (_) {} + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..id = 'first' + ..className = 'middle' + ..sx = {'mt': 2, 'p': 3} + ..onClick = (_) {} + )(); + } + '''), + ); + }); + + test('inserts after latest of (forwarding or last system prop)', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props) { + (Box() + ..mt = 2 + ..addProps(props) + ..p = 3 + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props) { + (Box() + ..addProps(props) + + // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering + ..sx = {...?props.sx, 'mt': 2, 'p': 3,} + )(); + } + '''), + ); + }); + + test('inserts after all forwarding calls when multiple exist', () async { + await testSuggestor( + input: withHeader(''' + content(BoxProps props1, BoxProps props2) { + (Box() + ..addProps(props1) + ..mt = 2 + ..p = 3 + ..addProps(props2) + )(); + } + '''), + expectedOutput: withHeader(''' + content(BoxProps props1, BoxProps props2) { + (Box() + ..addProps(props1) + ..addProps(props2) + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = {'mt': 2, 'p': 3} + )(); + } + '''), + ); + }); + }); + + group('multiline formatting:', () { + test('uses single line for short sx maps (< 3 elements)', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..mt = 2 + ..p = 3 + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = {'mt': 2, 'p': 3} + )(); + } + '''), + ); + }); + + test('uses multiline for 3+ elements', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..mt = 2 + ..p = 3 + ..mb = 4 + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = { + 'mt': 2, + 'p': 3, + 'mb': 4, + } + )(); + } + '''), + ); + }); + + test('uses multiline for long content (>= 20 chars with 2+ elements)', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box() + ..mt = 2 + ..bgcolor = 'verylongcolor.main' + )(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box() + ..sx = { + 'mt': 2, + 'bgcolor': 'verylongcolor.main', + } + )(); + } + '''), + ); + }); + }); + + group('handles different component types with system props:', () { + test('Box', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Box()..mt = 2)(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Box()..sx = {'mt': 2})(); + } + '''), + ); + }); + + test('Grid', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Grid()..mt = 2)(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Grid()..sx = {'mt': 2})(); + } + '''), + ); + }); + + test('Stack', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Stack()..mt = 2)(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Stack()..sx = {'mt': 2})(); + } + '''), + ); + }); + + test('Typography', () async { + await testSuggestor( + input: withHeader(''' + content() { + (Typography()..mt = 2)(); + } + '''), + expectedOutput: withHeader(''' + content() { + (Typography()..sx = {'mt': 2})(); + } + '''), + ); + }); + }); }); } From 4f2de690aecb8b6dd2d62675bc7e5520d3a39cb2 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 10:51:06 -0700 Subject: [PATCH 07/43] Remove incorrect cases --- lib/src/mui_suggestors/system_props_to_sx_migrator.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index d82231aa..2e6e532f 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -154,7 +154,9 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { ? ',' : ''; yieldPatch( - ', ${migratedSystemPropEntries.join(', ')}$maybeTrailingComma}', value.end, value.end); + ', ${migratedSystemPropEntries.join(', ')}$maybeTrailingComma}', + value.end, + value.end); } } else { final forwardedPropSources = _getForwardedPropSources(usage); @@ -250,8 +252,7 @@ List<_ForwardedPropSource> _getForwardedPropSources( case 'addAll': case 'addProps': if (arg is MethodInvocation && - (arg.methodName.name == 'getPropsToForward' || - arg.methodName.name == 'copyUnconsumedProps')) { + arg.methodName.name == 'getPropsToForward') { return _ForwardedPropSource(c, arg.realTarget); } return _ForwardedPropSource(c, arg); @@ -275,7 +276,6 @@ List<_ForwardedPropSource> _getForwardedPropSources( const _componentsWithDeprecatedSystemProps = { 'Box', - 'Container', 'Grid', 'Stack', 'Typography', From 4f6b56fb30fe567b29021fbf06178a5cb57cdf84 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 10:53:44 -0700 Subject: [PATCH 08/43] Add language injection comments --- .../system_props_to_sx_migrator_test.dart | 152 +++++++++--------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 2a4a1aa3..4429d57c 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -30,12 +30,12 @@ void main() { test('migrates single system prop to sx', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box()..mt = 2)(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box()..sx = {'mt': 2})(); } @@ -45,7 +45,7 @@ void main() { test('migrates multiple system props', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..mt = 2 @@ -54,7 +54,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -71,7 +71,7 @@ void main() { group('merges with existing sx map literal', () { test('without trailing commas', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = {'border': '1px solid black'} @@ -79,7 +79,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -94,7 +94,7 @@ void main() { test('with trailing commas', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -104,7 +104,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -119,7 +119,7 @@ void main() { test('that is empty', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = {} @@ -127,7 +127,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = {'mt': 2} @@ -139,7 +139,7 @@ void main() { test('with multiple existing entries', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -150,7 +150,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -168,7 +168,7 @@ void main() { group('merges with forwarded sx prop using spread:', () { test('nullable', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..sx = getSx() @@ -177,7 +177,7 @@ void main() { } Map? getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..sx = {...?getSx(), 'mt': 2} @@ -190,7 +190,7 @@ void main() { test('non-nullable', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..sx = getSx() @@ -199,7 +199,7 @@ void main() { } Map getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..sx = {...getSx(), 'mt': 2} @@ -212,7 +212,7 @@ void main() { test('dynamic', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..sx = getSx() @@ -221,7 +221,7 @@ void main() { } dynamic getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..sx = {...?getSx(), 'mt': 2} @@ -235,7 +235,7 @@ void main() { test('does not consider forwarding with an existing sx', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -244,7 +244,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -262,7 +262,7 @@ void main() { group('forwarded with', () { test('addProps', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -270,7 +270,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -283,7 +283,7 @@ void main() { test('addAll', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addAll(props) @@ -291,7 +291,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addAll(props) @@ -304,7 +304,7 @@ void main() { test('getPropsToForward', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props.getPropsToForward()) @@ -312,7 +312,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props.getPropsToForward()) @@ -325,7 +325,7 @@ void main() { test('addPropsToForward', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..modifyProps(props.addPropsToForward()) @@ -333,7 +333,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..modifyProps(props.addPropsToForward()) @@ -349,7 +349,7 @@ void main() { test('even when there are other unrelated calls in the cascade', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -358,7 +358,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -374,7 +374,7 @@ void main() { group('adds FIXME comment when forwarding is ambiguous:', () { test('modifyProps with unknown function', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..modifyProps((_) {}) @@ -382,7 +382,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..modifyProps((_) {}) @@ -399,7 +399,7 @@ void main() { test('copyUnconsumedProps', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' abstract class FooComponent extends UiComponent2 { content(BoxProps props) { (Box() @@ -409,7 +409,7 @@ void main() { } } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' abstract class FooComponent extends UiComponent2 { content(BoxProps props) { (Box() @@ -429,7 +429,7 @@ void main() { group('generic props:', () { test('Map', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(Map props) { (Box() ..addAll(props) @@ -437,7 +437,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(Map props) { (Box() ..addAll(props) @@ -454,7 +454,7 @@ void main() { test('UiProps', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(UiProps props) { (Box() ..addAll(props) @@ -462,7 +462,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(UiProps props) { (Box() ..addAll(props) @@ -479,7 +479,7 @@ void main() { test('dynamic', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(dynamic props) { (Box() ..addAll(props) @@ -487,7 +487,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(dynamic props) { (Box() ..addAll(props) @@ -505,7 +505,7 @@ void main() { test('multiple prop forwarding calls', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props, BoxProps props2) { (Box() ..addProps(props) @@ -514,7 +514,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props, BoxProps props2) { (Box() ..addProps(props) @@ -533,7 +533,7 @@ void main() { test('handles complex prop values with expressions', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(bool condition) { (Box() ..mt = condition ? 2 : 4 @@ -542,7 +542,7 @@ void main() { } int getSpacing() => 3; '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(bool condition) { (Box() ..sx = { @@ -558,14 +558,14 @@ void main() { test('handles responsive system prop values', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..mt = {'xs': 1, 'sm': 2, 'md': 3} )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = {'mt': {'xs': 1, 'sm': 2, 'md': 3}} @@ -577,7 +577,7 @@ void main() { test('preserves non-system props', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..id = 'test' @@ -587,7 +587,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..id = 'test' @@ -602,13 +602,13 @@ void main() { test('handles multiple components in the same file', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box()..mt = 2)(); (Box()..p = 3)(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box()..sx = {'mt': 2})(); (Box()..sx = {'p': 3})(); @@ -619,7 +619,7 @@ void main() { test('respects orcm_ignore comments', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { // orcm_ignore (Box()..mt = 2)(); @@ -631,7 +631,7 @@ void main() { test('does not migrate components without deprecated system props', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..id = 'test' @@ -645,7 +645,7 @@ void main() { test('does not migrate deprecated props with the same name as system props', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (TextField() ..color = '' @@ -657,7 +657,7 @@ void main() { test('does not flag unrelated cascades with FIXMEs', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..addProp('foo', 'bar') @@ -675,7 +675,7 @@ void main() { group('insertion location:', () { test('inserts after prop forwarding to avoid being overwritten, with a comment', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..mt = 2 @@ -684,7 +684,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -700,7 +700,7 @@ void main() { test('inserts at location of last system prop when no prop forwarding', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..id = 'first' @@ -711,7 +711,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..id = 'first' @@ -726,7 +726,7 @@ void main() { test('inserts after latest of (forwarding or last system prop)', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..mt = 2 @@ -735,7 +735,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) { (Box() ..addProps(props) @@ -750,7 +750,7 @@ void main() { test('inserts after all forwarding calls when multiple exist', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content(BoxProps props1, BoxProps props2) { (Box() ..addProps(props1) @@ -760,7 +760,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props1, BoxProps props2) { (Box() ..addProps(props1) @@ -777,7 +777,7 @@ void main() { group('multiline formatting:', () { test('uses single line for short sx maps (< 3 elements)', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..mt = 2 @@ -785,7 +785,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = {'mt': 2, 'p': 3} @@ -797,7 +797,7 @@ void main() { test('uses multiline for 3+ elements', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..mt = 2 @@ -806,7 +806,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -822,7 +822,7 @@ void main() { test('uses multiline for long content (>= 20 chars with 2+ elements)', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box() ..mt = 2 @@ -830,7 +830,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box() ..sx = { @@ -847,12 +847,12 @@ void main() { group('handles different component types with system props:', () { test('Box', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Box()..mt = 2)(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Box()..sx = {'mt': 2})(); } @@ -862,12 +862,12 @@ void main() { test('Grid', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Grid()..mt = 2)(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Grid()..sx = {'mt': 2})(); } @@ -877,12 +877,12 @@ void main() { test('Stack', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Stack()..mt = 2)(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Stack()..sx = {'mt': 2})(); } @@ -892,12 +892,12 @@ void main() { test('Typography', () async { await testSuggestor( - input: withHeader(''' + input: withHeader(/*language=dart*/ ''' content() { (Typography()..mt = 2)(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withHeader(/*language=dart*/ ''' content() { (Typography()..sx = {'mt': 2})(); } From bd3c6468304947a7a21c89f6e833aada99f17a59 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 10:56:52 -0700 Subject: [PATCH 09/43] Use fat arrow functions to save on lines --- .../system_props_to_sx_migrator_test.dart | 742 ++++++++---------- 1 file changed, 339 insertions(+), 403 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 4429d57c..418c370a 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -31,14 +31,12 @@ void main() { test('migrates single system prop to sx', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box()..mt = 2)(); - } + content() => + (Box()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box()..sx = {'mt': 2})(); - } + content() => + (Box()..sx = {'mt': 2})(); '''), ); }); @@ -46,24 +44,22 @@ void main() { test('migrates multiple system props', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..mt = 2 - ..p = 3 - ..bgcolor = 'primary.main' - )(); - } + content() => + (Box() + ..mt = 2 + ..p = 3 + ..bgcolor = 'primary.main' + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'mt': 2, - 'p': 3, - 'bgcolor': 'primary.main', - } - )(); - } + content() => + (Box() + ..sx = { + 'mt': 2, + 'p': 3, + 'bgcolor': 'primary.main', + } + )(); '''), ); }); @@ -72,22 +68,20 @@ void main() { test('without trailing commas', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = {'border': '1px solid black'} - ..mt = 2 - )(); - } + content() => + (Box() + ..sx = {'border': '1px solid black'} + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'border': '1px solid black', - 'mt': 2, - } - )(); - } + content() => + (Box() + ..sx = { + 'border': '1px solid black', + 'mt': 2, + } + )(); '''), ); }); @@ -95,24 +89,22 @@ void main() { test('with trailing commas', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'border': '1px solid black', - } - ..mt = 2 - )(); - } + content() => + (Box() + ..sx = { + 'border': '1px solid black', + } + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'border': '1px solid black', - 'mt': 2, - } - )(); - } + content() => + (Box() + ..sx = { + 'border': '1px solid black', + 'mt': 2, + } + )(); '''), ); }); @@ -120,19 +112,17 @@ void main() { test('that is empty', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = {} - ..mt = 2 - )(); - } + content() => + (Box() + ..sx = {} + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = {'mt': 2} - )(); - } + content() => + (Box() + ..sx = {'mt': 2} + )(); '''), ); }); @@ -140,26 +130,24 @@ void main() { test('with multiple existing entries', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'backgroundColor': 'white', - 'color': 'blue', - } - ..mt = 2 - )(); - } + content() => + (Box() + ..sx = { + 'backgroundColor': 'white', + 'color': 'blue', + } + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'backgroundColor': 'white', - 'color': 'blue', - 'mt': 2, - } - )(); - } + content() => + (Box() + ..sx = { + 'backgroundColor': 'white', + 'color': 'blue', + 'mt': 2, + } + )(); '''), ); }); @@ -169,20 +157,18 @@ void main() { test('nullable', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..sx = getSx() - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..sx = getSx() + ..mt = 2 + )(); Map? getSx() => {'color': 'red'}; '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..sx = {...?getSx(), 'mt': 2} - )(); - } + content(BoxProps props) => + (Box() + ..sx = {...?getSx(), 'mt': 2} + )(); Map? getSx() => {'color': 'red'}; '''), ); @@ -191,20 +177,18 @@ void main() { test('non-nullable', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..sx = getSx() - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..sx = getSx() + ..mt = 2 + )(); Map getSx() => {'color': 'red'}; '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..sx = {...getSx(), 'mt': 2} - )(); - } + content(BoxProps props) => + (Box() + ..sx = {...getSx(), 'mt': 2} + )(); Map getSx() => {'color': 'red'}; '''), ); @@ -213,20 +197,18 @@ void main() { test('dynamic', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..sx = getSx() - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..sx = getSx() + ..mt = 2 + )(); dynamic getSx() => {'color': 'red'}; '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..sx = {...?getSx(), 'mt': 2} - )(); - } + content(BoxProps props) => + (Box() + ..sx = {...?getSx(), 'mt': 2} + )(); dynamic getSx() => {'color': 'red'}; '''), ); @@ -236,24 +218,22 @@ void main() { test('does not consider forwarding with an existing sx', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..sx = {'border': '1px solid black'} - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = {'border': '1px solid black'} + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..sx = { - 'border': '1px solid black', - 'mt': 2, - } - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = { + 'border': '1px solid black', + 'mt': 2, + } + )(); '''), ); }); @@ -263,20 +243,18 @@ void main() { test('addProps', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..sx = {...?props.sx, 'mt': 2,} - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = {...?props.sx, 'mt': 2,} + )(); '''), ); }); @@ -284,20 +262,18 @@ void main() { test('addAll', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addAll(props) - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..addAll(props) + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addAll(props) - ..sx = {...?props.sx, 'mt': 2,} - )(); - } + content(BoxProps props) => + (Box() + ..addAll(props) + ..sx = {...?props.sx, 'mt': 2,} + )(); '''), ); }); @@ -305,20 +281,18 @@ void main() { test('getPropsToForward', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props.getPropsToForward()) - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props.getPropsToForward()) + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props.getPropsToForward()) - ..sx = {...?props.sx, 'mt': 2,} - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props.getPropsToForward()) + ..sx = {...?props.sx, 'mt': 2,} + )(); '''), ); }); @@ -326,20 +300,18 @@ void main() { test('addPropsToForward', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..modifyProps(props.addPropsToForward()) - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..modifyProps(props.addPropsToForward()) + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..modifyProps(props.addPropsToForward()) - ..sx = {...?props.sx, 'mt': 2,} - )(); - } + content(BoxProps props) => + (Box() + ..modifyProps(props.addPropsToForward()) + ..sx = {...?props.sx, 'mt': 2,} + )(); '''), ); }); @@ -350,22 +322,20 @@ void main() { () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..addTestId('test-id') - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + ..addTestId('test-id') + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - ..addTestId('test-id') - ..sx = {...?props.sx, 'mt': 2,} - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + ..addTestId('test-id') + ..sx = {...?props.sx, 'mt': 2,} + )(); '''), ); }); @@ -375,24 +345,22 @@ void main() { test('modifyProps with unknown function', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..modifyProps((_) {}) - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..modifyProps((_) {}) + ..mt = 2 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..modifyProps((_) {}) - - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed - ..sx = { - 'mt': 2 - } - )(); - } + content(BoxProps props) => + (Box() + ..modifyProps((_) {}) + + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } + )(); '''), ); }); @@ -401,26 +369,24 @@ void main() { await testSuggestor( input: withHeader(/*language=dart*/ ''' abstract class FooComponent extends UiComponent2 { - content(BoxProps props) { - (Box() - ..addProps(copyUnconsumedProps()) - ..mt = 2 - )(); - } + content(BoxProps props) => + (Box() + ..addProps(copyUnconsumedProps()) + ..mt = 2 + )(); } '''), expectedOutput: withHeader(/*language=dart*/ ''' abstract class FooComponent extends UiComponent2 { - content(BoxProps props) { - (Box() - ..addProps(copyUnconsumedProps()) - - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed - ..sx = { - 'mt': 2 - } - )(); - } + content(BoxProps props) => + (Box() + ..addProps(copyUnconsumedProps()) + + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = { + 'mt': 2 + } + )(); } '''), ); @@ -559,18 +525,16 @@ void main() { test('handles responsive system prop values', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..mt = {'xs': 1, 'sm': 2, 'md': 3} - )(); - } + content() => + (Box() + ..mt = {'xs': 1, 'sm': 2, 'md': 3} + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = {'mt': {'xs': 1, 'sm': 2, 'md': 3}} - )(); - } + content() => + (Box() + ..sx = {'mt': {'xs': 1, 'sm': 2, 'md': 3}} + )(); '''), ); }); @@ -578,24 +542,22 @@ void main() { test('preserves non-system props', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..id = 'test' - ..mt = 2 - ..className = 'custom-class' - ..onClick = (_) {} - )(); - } + content() => + (Box() + ..id = 'test' + ..mt = 2 + ..className = 'custom-class' + ..onClick = (_) {} + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..id = 'test' - ..sx = {'mt': 2} - ..className = 'custom-class' - ..onClick = (_) {} - )(); - } + content() => + (Box() + ..id = 'test' + ..sx = {'mt': 2} + ..className = 'custom-class' + ..onClick = (_) {} + )(); '''), ); }); @@ -609,7 +571,7 @@ void main() { } '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { + content() { (Box()..sx = {'mt': 2})(); (Box()..sx = {'p': 3})(); } @@ -620,10 +582,9 @@ void main() { test('respects orcm_ignore comments', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - // orcm_ignore - (Box()..mt = 2)(); - } + content() => + // orcm_ignore + (Box()..mt = 2)(); '''), ); }); @@ -632,12 +593,11 @@ void main() { () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..id = 'test' - ..className = 'test-class' - )(); - } + content() => + (Box() + ..id = 'test' + ..className = 'test-class' + )(); '''), ); }); @@ -646,11 +606,10 @@ void main() { () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (TextField() - ..color = '' - )(); - } + content() => + (TextField() + ..color = '' + )(); '''), ); }); @@ -658,14 +617,13 @@ void main() { test('does not flag unrelated cascades with FIXMEs', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..addProp('foo', 'bar') - ..['bar'] = 'baz' - ..ref = (_) {} - ..className = 'test-class' - )(); - } + content() => + (Box() + ..addProp('foo', 'bar') + ..['bar'] = 'baz' + ..ref = (_) {} + ..className = 'test-class' + )(); '''), ); }); @@ -676,24 +634,22 @@ void main() { test('inserts after prop forwarding to avoid being overwritten, with a comment', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..mt = 2 - ..addProps(props) - ..id = 'test' - )(); - } + content(BoxProps props) => + (Box() + ..mt = 2 + ..addProps(props) + ..id = 'test' + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - - // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering - ..sx = {...?props.sx, 'mt': 2,} - ..id = 'test' - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + + // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering + ..sx = {...?props.sx, 'mt': 2,} + ..id = 'test' + )(); '''), ); }); @@ -701,25 +657,23 @@ void main() { test('inserts at location of last system prop when no prop forwarding', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..id = 'first' - ..mt = 2 - ..className = 'middle' - ..p = 3 - ..onClick = (_) {} - )(); - } + content() => + (Box() + ..id = 'first' + ..mt = 2 + ..className = 'middle' + ..p = 3 + ..onClick = (_) {} + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..id = 'first' - ..className = 'middle' - ..sx = {'mt': 2, 'p': 3} - ..onClick = (_) {} - )(); - } + content() => + (Box() + ..id = 'first' + ..className = 'middle' + ..sx = {'mt': 2, 'p': 3} + ..onClick = (_) {} + )(); '''), ); }); @@ -727,23 +681,21 @@ void main() { test('inserts after latest of (forwarding or last system prop)', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..mt = 2 - ..addProps(props) - ..p = 3 - )(); - } + content(BoxProps props) => + (Box() + ..mt = 2 + ..addProps(props) + ..p = 3 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) { - (Box() - ..addProps(props) - - // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering - ..sx = {...?props.sx, 'mt': 2, 'p': 3,} - )(); - } + content(BoxProps props) => + (Box() + ..addProps(props) + + // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering + ..sx = {...?props.sx, 'mt': 2, 'p': 3,} + )(); '''), ); }); @@ -751,24 +703,22 @@ void main() { test('inserts after all forwarding calls when multiple exist', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content(BoxProps props1, BoxProps props2) { - (Box() - ..addProps(props1) - ..mt = 2 - ..p = 3 - ..addProps(props2) - )(); - } + content(BoxProps props1, BoxProps props2) => + (Box() + ..addProps(props1) + ..mt = 2 + ..p = 3 + ..addProps(props2) + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props1, BoxProps props2) { - (Box() - ..addProps(props1) - ..addProps(props2) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed - ..sx = {'mt': 2, 'p': 3} - )(); - } + content(BoxProps props1, BoxProps props2) => + (Box() + ..addProps(props1) + ..addProps(props2) + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + ..sx = {'mt': 2, 'p': 3} + )(); '''), ); }); @@ -778,19 +728,17 @@ void main() { test('uses single line for short sx maps (< 3 elements)', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..mt = 2 - ..p = 3 - )(); - } + content() => + (Box() + ..mt = 2 + ..p = 3 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = {'mt': 2, 'p': 3} - )(); - } + content() => + (Box() + ..sx = {'mt': 2, 'p': 3} + )(); '''), ); }); @@ -798,24 +746,22 @@ void main() { test('uses multiline for 3+ elements', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..mt = 2 - ..p = 3 - ..mb = 4 - )(); - } + content() => + (Box() + ..mt = 2 + ..p = 3 + ..mb = 4 + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'mt': 2, - 'p': 3, - 'mb': 4, - } - )(); - } + content() => + (Box() + ..sx = { + 'mt': 2, + 'p': 3, + 'mb': 4, + } + )(); '''), ); }); @@ -823,22 +769,20 @@ void main() { test('uses multiline for long content (>= 20 chars with 2+ elements)', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..mt = 2 - ..bgcolor = 'verylongcolor.main' - )(); - } + content() => + (Box() + ..mt = 2 + ..bgcolor = 'verylongcolor.main' + )(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box() - ..sx = { - 'mt': 2, - 'bgcolor': 'verylongcolor.main', - } - )(); - } + content() => + (Box() + ..sx = { + 'mt': 2, + 'bgcolor': 'verylongcolor.main', + } + )(); '''), ); }); @@ -848,14 +792,12 @@ void main() { test('Box', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Box()..mt = 2)(); - } + content() => + (Box()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Box()..sx = {'mt': 2})(); - } + content() => + (Box()..sx = {'mt': 2})(); '''), ); }); @@ -863,14 +805,12 @@ void main() { test('Grid', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Grid()..mt = 2)(); - } + content() => + (Grid()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Grid()..sx = {'mt': 2})(); - } + content() => + (Grid()..sx = {'mt': 2})(); '''), ); }); @@ -878,14 +818,12 @@ void main() { test('Stack', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Stack()..mt = 2)(); - } + content() => + (Stack()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Stack()..sx = {'mt': 2})(); - } + content() => + (Stack()..sx = {'mt': 2})(); '''), ); }); @@ -893,14 +831,12 @@ void main() { test('Typography', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() { - (Typography()..mt = 2)(); - } + content() => + (Typography()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() { - (Typography()..sx = {'mt': 2})(); - } + content() => + (Typography()..sx = {'mt': 2})(); '''), ); }); From b86169bc86fceca75604416750823bc69d27105f Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 10:59:35 -0700 Subject: [PATCH 10/43] Condense further --- .../system_props_to_sx_migrator_test.dart | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 418c370a..507249ea 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -582,9 +582,8 @@ void main() { test('respects orcm_ignore comments', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() => - // orcm_ignore - (Box()..mt = 2)(); + // orcm_ignore + content() => (Box()..mt = 2)(); '''), ); }); @@ -606,10 +605,7 @@ void main() { () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() => - (TextField() - ..color = '' - )(); + content() => (TextField()..color = '')(); '''), ); }); @@ -792,12 +788,10 @@ void main() { test('Box', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() => - (Box()..mt = 2)(); + content() => (Box()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() => - (Box()..sx = {'mt': 2})(); + content() => (Box()..sx = {'mt': 2})(); '''), ); }); @@ -805,12 +799,10 @@ void main() { test('Grid', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() => - (Grid()..mt = 2)(); + content() => (Grid()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() => - (Grid()..sx = {'mt': 2})(); + content() => (Grid()..sx = {'mt': 2})(); '''), ); }); @@ -818,12 +810,10 @@ void main() { test('Stack', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() => - (Stack()..mt = 2)(); + content() => (Stack()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() => - (Stack()..sx = {'mt': 2})(); + content() => (Stack()..sx = {'mt': 2})(); '''), ); }); @@ -831,12 +821,10 @@ void main() { test('Typography', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' - content() => - (Typography()..mt = 2)(); + content() => (Typography()..mt = 2)(); '''), expectedOutput: withHeader(/*language=dart*/ ''' - content() => - (Typography()..sx = {'mt': 2})(); + content() => (Typography()..sx = {'mt': 2})(); '''), ); }); From bfa1c3af0eab56439c6d717d3e0307b973681ceb Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 11:48:05 -0700 Subject: [PATCH 11/43] Add fixme for potential behavior change --- .../system_props_to_sx_migrator.dart | 42 +++++++++++-------- lib/src/util.dart | 4 +- .../system_props_to_sx_migrator_test.dart | 5 +-- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 2e6e532f..9404762a 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'dart:math'; - import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; @@ -172,7 +170,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { }); final String? additionalSxElement; - final bool needsFixme; + final fixmes = []; if (mightForwardSx) { var canGetForwardedSx = false; @@ -192,20 +190,14 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (canGetForwardedSx) { additionalSxElement = '...?${forwardedProps!.toSource()}.sx'; - needsFixme = false; } else { additionalSxElement = null; - needsFixme = true; + fixmes.add( + 'merge in any sx prop forwarded to this component, if needed'); } } else { additionalSxElement = null; - needsFixme = false; } - final fixme = needsFixme - ? '\n ' + - lineComment( - '$fixmePrefix - merge in any sx prop forwarded to this component, if needed') - : ''; final elements = [ if (additionalSxElement != null) additionalSxElement, @@ -217,15 +209,31 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { ? ',' : ''; + final forwardingEnds = + forwardedPropSources.map((s) => s.cascadedMethod.node.end).toList(); + final systemPropEnds = + deprecatedSystemProps.map((p) => p.node.end).toList(); + // Insert after any forwarded props to ensure sx isn't overwritten, // and where the last system prop used to be to preserve the location and reduce diffs. - final insertionLocation = [ - ...forwardedPropSources.map((source) => source.cascadedMethod.node.end), - deprecatedSystemProps.last.node.end - ].reduce(max); + final insertionLocation = [...forwardingEnds, ...systemPropEnds].max; + + // Add a note to check for potential behavior changes when system props + // used to come before prop forwarding, but now get added in sx after. + final firstForwardingEnd = forwardingEnds.minOrNull; + if (firstForwardingEnd != null && + insertionLocation >= firstForwardingEnd && + systemPropEnds.any((system) => system < firstForwardingEnd)) { + fixmes.add( + 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. Double-check that this new behavior is okay.'); + } - yieldPatch('$fixme..sx = {${elements.join(', ')}$maybeTrailingComma}', - insertionLocation, insertionLocation); + final fixmesSource = + fixmes.map((f) => '\n ' + lineComment('$fixmePrefix - $f')).join(''); + yieldPatch( + '$fixmesSource..sx = {${elements.join(', ')}$maybeTrailingComma}', + insertionLocation, + insertionLocation); } } } diff --git a/lib/src/util.dart b/lib/src/util.dart index 5fcb41fb..bb410b56 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -549,5 +549,5 @@ extension ParentFieldDeclExtension on VariableDeclaration { String blockComment(String contents) => '/*$contents*/'; -String lineComment(String contents) => - contents.split('\n').map((line) => '// $line\n').join(''); +String lineComment(String contents, {String indent = ''}) => + contents.split('\n').map((line) => '$indent// $line\n').join(''); diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 507249ea..64399262 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -641,8 +641,7 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) - - // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. Double-check that this new behavior is okay. ..sx = {...?props.sx, 'mt': 2,} ..id = 'test' )(); @@ -689,7 +688,7 @@ void main() { (Box() ..addProps(props) - // FIXME(mui_system_props_migration) - forwarded props used to be able to override these system props; double-check merge ordering + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. Double-check that this new behavior is okay. ..sx = {...?props.sx, 'mt': 2, 'p': 3,} )(); '''), From 49383226785917a5b211004e5da1982311891b39 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 12:11:09 -0700 Subject: [PATCH 12/43] Improve comment --- lib/src/mui_suggestors/system_props_to_sx_migrator.dart | 9 ++++++--- .../mui_suggestors/system_props_to_sx_migrator_test.dart | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 9404762a..61bf535e 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -225,11 +225,14 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { insertionLocation >= firstForwardingEnd && systemPropEnds.any((system) => system < firstForwardingEnd)) { fixmes.add( - 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. Double-check that this new behavior is okay.'); + 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' + '\n Double-check that this new behavior is okay, and update logic as needed (e.g., merging in props.sx after these styles instead of before).'); } - final fixmesSource = - fixmes.map((f) => '\n ' + lineComment('$fixmePrefix - $f')).join(''); + final fixmesSource = fixmes + // Indents with a single space so that dartfmt doesn't make it stick to the beginning of the line. + .map((f) => '\n' + lineComment('$fixmePrefix - $f', indent: ' ')) + .join(''); yieldPatch( '$fixmesSource..sx = {${elements.join(', ')}$maybeTrailingComma}', insertionLocation, diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 64399262..6d8bc3c6 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -641,7 +641,8 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. Double-check that this new behavior is okay. + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. + // Double-check that this new behavior is okay, and update logic as needed (e.g., merging in props.sx after these styles instead of before). ..sx = {...?props.sx, 'mt': 2,} ..id = 'test' )(); @@ -688,7 +689,8 @@ void main() { (Box() ..addProps(props) - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. Double-check that this new behavior is okay. + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. + // Double-check that this new behavior is okay, and update logic as needed (e.g., merging in props.sx after these styles instead of before). ..sx = {...?props.sx, 'mt': 2, 'p': 3,} )(); '''), From d50bae438854e635014605b1d12e19899db77091 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 13:52:51 -0700 Subject: [PATCH 13/43] Handle comments --- .../system_props_to_sx_migrator.dart | 48 +++- .../system_props_to_sx_migrator_test.dart | 225 ++++++++++++++++++ 2 files changed, 265 insertions(+), 8 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 61bf535e..15701d9f 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -67,6 +67,12 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { @override get shouldFlagClassName => false; + bool isFirstTokenOnLine(Token token) { + final sourceFile = context.sourceFile; + final lineStart = sourceFile.getOffset(sourceFile.getLine(token.offset)); + return sourceFile.getText(lineStart, token.offset).trim().isEmpty; + } + @override void migrateUsage(FluentComponentUsage usage) { super.migrateUsage(usage); @@ -96,23 +102,47 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (deprecatedSystemProps.isEmpty) return; - // FIXME comments don't come with props + var hasAnyComments = false; - final migratedSystemPropEntries = deprecatedSystemProps.map((prop) { + final migratedSystemPropEntries = []; + for (final prop in deprecatedSystemProps) { final propName = prop.name.name; final propValue = context.sourceFile .getText(prop.rightHandSide.offset, prop.rightHandSide.end); - return "'$propName': $propValue"; - }); - // Remove all system props - for (final prop in deprecatedSystemProps) { - yieldRemovePropPatch(prop); + // Carry over comments before the node + // (we won't try to handle end-of-line comments). + final beforeComments = allCommentsForNode(prop.node) + // Don't include end-of-line comments that should stay with the previous line. + .skipWhile((comment) => !isFirstTokenOnLine(comment)) + .toList(); + + if (beforeComments.isNotEmpty) hasAnyComments = true; + + final commentSource = beforeComments.isEmpty + ? '' + // Get full comment text, and trim trailing newline/whitespace + : context.sourceFile + .getText( + beforeComments.first.offset, beforeComments.last.end) + .trimRight(); + + // Create new sx entry + migratedSystemPropEntries.add([ + if (commentSource.isNotEmpty) '\n $commentSource', + "'$propName': $propValue" + ].join('\n')); + + // Remove old system prop + yieldPatch('', beforeComments.firstOrNull?.offset ?? prop.node.offset, + prop.node.end); } bool shouldForceMultiline( {required int elementCount, required int charCount}) { - return elementCount >= 3 || (elementCount > 1 && charCount >= 20); + return hasAnyComments || + elementCount >= 3 || + (elementCount > 1 && charCount >= 20); } if (existingSxProp != null) { @@ -241,6 +271,8 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { } } +enum CommentLocation { ownLine, endOfLine, other } + class _ForwardedPropSource { final BuilderMethodInvocation cascadedMethod; final Expression? propsExpression; diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 6d8bc3c6..d9632e71 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -785,6 +785,231 @@ void main() { }); }); + group('preserves comments before system props:', () { + test('single line comment before single system prop', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + // Add margin + ..mt = 2 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..sx = { + // Add margin + 'mt': 2, + } + )(); + '''), + ); + }); + + test('single line comment before one of multiple system props', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..mt = 2 + // Add padding + ..p = 3 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..sx = { + 'mt': 2, + // Add padding + 'p': 3, + } + )(); + '''), + ); + }); + + test('multiple comments before different system props', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + // Top margin + ..mt = 2 + // Horizontal padding + ..px = 3 + // Background color + ..bgcolor = 'blue' + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..sx = { + // Top margin + 'mt': 2, + // Horizontal padding + 'px': 3, + // Background color + 'bgcolor': 'blue', + } + )(); + '''), + ); + }); + + test('multi-line comment before system prop', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + /* This is a longer comment + explaining the margin */ + ..mt = 2 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..sx = { + /* This is a longer comment + explaining the margin */ + 'mt': 2, + } + )(); + '''), + ); + }); + + test('comment before system prop with existing sx', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..sx = {'border': '1px solid black'} + // Add margin + ..mt = 2 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..sx = { + 'border': '1px solid black', + // Add margin + 'mt': 2, + } + )(); + '''), + ); + }); + + test('comments before system props mixed with non-system props', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..id = 'test' + // Spacing + ..mt = 2 + ..className = 'custom' + // More spacing + ..p = 3 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..id = 'test' + ..className = 'custom' + ..sx = { + // Spacing + 'mt': 2, + // More spacing + 'p': 3, + } + )(); + '''), + ); + }); + + test('comment before system prop with forwarding', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + // Override margin + ..mt = 2 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = { + ...?props.sx, + // Override margin + 'mt': 2, + } + )(); + '''), + ); + }); + + test('inline comment on same line as system prop', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + ..mt = 2 // top margin + ..p = 3 + )(); + '''), + // Inline comment behavior here isn't great, but it's too much effort + // to deal with in this codemod. + // Just verify the codemod doesn't break or do anything too outlandish. + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + // top margin + ..sx = {'mt': 2, 'p': 3} + )(); + '''), + ); + }); + + test('multiple comment types with system props', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content() => + (Box() + // Line comment + ..mt = 2 // inline comment + /* Block comment */ + ..p = 3 + )(); + '''), + // Inline comment behavior here isn't great, but it's too much effort + // to deal with in this codemod. + // Just verify the codemod doesn't break or do anything too outlandish. + expectedOutput: withHeader(/*language=dart*/ ''' + content() => + (Box() + // inline comment + ..sx = { + // Line comment + 'mt': 2, + /* Block comment */ + 'p': 3, + } + )(); + '''), + ); + }); + }); + group('handles different component types with system props:', () { test('Box', () async { await testSuggestor( From 1215a8bb5fa345bdbf9727f1a7acead5af9e0070 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 14:03:26 -0700 Subject: [PATCH 14/43] Simplify multiline logic --- .../system_props_to_sx_migrator.dart | 50 ++++++++----------- .../system_props_to_sx_migrator_test.dart | 10 +++- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 15701d9f..8c915f97 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -102,8 +102,6 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (deprecatedSystemProps.isEmpty) return; - var hasAnyComments = false; - final migratedSystemPropEntries = []; for (final prop in deprecatedSystemProps) { final propName = prop.name.name; @@ -117,14 +115,11 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { .skipWhile((comment) => !isFirstTokenOnLine(comment)) .toList(); - if (beforeComments.isNotEmpty) hasAnyComments = true; - final commentSource = beforeComments.isEmpty ? '' // Get full comment text, and trim trailing newline/whitespace : context.sourceFile - .getText( - beforeComments.first.offset, beforeComments.last.end) + .getText(beforeComments.first.offset, beforeComments.last.end) .trimRight(); // Create new sx entry @@ -138,12 +133,13 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { prop.node.end); } - bool shouldForceMultiline( - {required int elementCount, required int charCount}) { - return hasAnyComments || - elementCount >= 3 || - (elementCount > 1 && charCount >= 20); - } + bool shouldForceMultiline(List mapElements) => + // Force multiline if there are a certain number of entries, or... + mapElements.length >= 3 || + // there's more than one entry and the line is getting too long, or... + (mapElements.length > 1 && mapElements.join(', ').length >= 20) || + // any entry is multiline (including comments). + mapElements.any((e) => e.contains('\n')); if (existingSxProp != null) { // FIXME before vs after @@ -156,12 +152,10 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final maybePrecedingComma = !hadTrailingComma && value.elements.isNotEmpty ? ', ' : ''; final maybeTrailingComma = hadTrailingComma || - shouldForceMultiline( - elementCount: - value.elements.length + migratedSystemPropEntries.length, - charCount: value.toSource().length + - migratedSystemPropEntries.join(', ').length, - ) + shouldForceMultiline([ + ...value.elements.map((e) => context.sourceFor(e)), + ...migratedSystemPropEntries, + ]) ? ',' : ''; yieldPatch( @@ -173,12 +167,12 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final nonNullable = type != null && type is! DynamicType && type.nullabilitySuffix == NullabilitySuffix.none; - yieldPatch('{...${nonNullable ? '' : '?'}', value.offset, value.offset); - final maybeTrailingComma = shouldForceMultiline( - elementCount: migratedSystemPropEntries.length + 1, - charCount: [value.toSource(), ...migratedSystemPropEntries] - .join(', ') - .length) + final spread = '...${nonNullable ? '' : '?'}'; + yieldPatch('{$spread', value.offset, value.offset); + final maybeTrailingComma = shouldForceMultiline([ + '$spread${context.sourceFor(value)}', + ...migratedSystemPropEntries, + ]) ? ',' : ''; yieldPatch( @@ -219,7 +213,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { } if (canGetForwardedSx) { - additionalSxElement = '...?${forwardedProps!.toSource()}.sx'; + additionalSxElement = '...?${context.sourceFor(forwardedProps!)}.sx'; } else { additionalSxElement = null; fixmes.add( @@ -233,11 +227,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (additionalSxElement != null) additionalSxElement, ...migratedSystemPropEntries, ]; - final maybeTrailingComma = shouldForceMultiline( - elementCount: elements.length, - charCount: elements.join(', ').length) - ? ',' - : ''; + final maybeTrailingComma = shouldForceMultiline(elements) ? ',' : ''; final forwardingEnds = forwardedPropSources.map((s) => s.cascadedMethod.node.end).toList(); diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index d9632e71..42f210c0 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -167,7 +167,10 @@ void main() { expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) => (Box() - ..sx = {...?getSx(), 'mt': 2} + ..sx = { + ...?getSx(), + 'mt': 2, + } )(); Map? getSx() => {'color': 'red'}; '''), @@ -207,7 +210,10 @@ void main() { expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) => (Box() - ..sx = {...?getSx(), 'mt': 2} + ..sx = { + ...?getSx(), + 'mt': 2, + } )(); dynamic getSx() => {'color': 'red'}; '''), From 0cecc49a00b750842b587070fb4803de53e0e878 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 14:38:47 -0700 Subject: [PATCH 15/43] Update sx order to preserve behavior, clean up --- .../system_props_to_sx_migrator.dart | 90 +++++++++---------- .../system_props_to_sx_migrator_test.dart | 72 ++++++++------- 2 files changed, 84 insertions(+), 78 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 8c915f97..6e38d90d 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -142,45 +142,50 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { mapElements.any((e) => e.contains('\n')); if (existingSxProp != null) { - // FIXME before vs after + // + // Case 1: add styles to existing sx prop value final value = existingSxProp.rightHandSide; if (value is SetOrMapLiteral && value.isMap) { - // Avoid inserting a double-comma if there's a trailing comma. - // While we're here, also preserve the trailing comma. + // + // Case 1a: add styles to existing sx map literal + + // Insert before, to preserve existing behavior where any spread sx trumped these styles. + // Add a leading newline to ensure comments don't get stuck to the opening braces. + yieldPatch('\n${migratedSystemPropEntries.join(',\n')},', + value.leftBracket.end, value.leftBracket.end); + // Force a multiline in all cases by ensuring there's a trailing comma. final hadTrailingComma = value.rightBracket.previous?.type == TokenType.COMMA; - final maybePrecedingComma = - !hadTrailingComma && value.elements.isNotEmpty ? ', ' : ''; - final maybeTrailingComma = hadTrailingComma || - shouldForceMultiline([ - ...value.elements.map((e) => context.sourceFor(e)), - ...migratedSystemPropEntries, - ]) - ? ',' - : ''; - yieldPatch( - '$maybePrecedingComma${migratedSystemPropEntries.join(', ')}$maybeTrailingComma', - value.rightBracket.offset, - value.rightBracket.offset); + if (!hadTrailingComma && value.elements.isNotEmpty) { + yieldPatch(',', value.rightBracket.offset, value.rightBracket.offset); + } } else { + // + // Case 1b: spread existing sx value into a new map literal + final type = value.staticType; final nonNullable = type != null && type is! DynamicType && type.nullabilitySuffix == NullabilitySuffix.none; final spread = '...${nonNullable ? '' : '?'}'; - yieldPatch('{$spread', value.offset, value.offset); + // Insert before spread, to preserve existing behavior where any forwarded sx trumped these styles. + yieldPatch('{\n${migratedSystemPropEntries.join(', ')}, $spread', + value.offset, value.offset); final maybeTrailingComma = shouldForceMultiline([ - '$spread${context.sourceFor(value)}', ...migratedSystemPropEntries, + '$spread${context.sourceFor(value)}', ]) ? ',' : ''; - yieldPatch( - ', ${migratedSystemPropEntries.join(', ')}$maybeTrailingComma}', - value.end, - value.end); + yieldPatch('$maybeTrailingComma}', value.end, value.end); } } else { + // + // Case 2: add new sx prop assignment + + final String? forwardedSxSpread; + final fixmes = []; + final forwardedPropSources = _getForwardedPropSources(usage); final mightForwardSx = forwardedPropSources.any((source) { final type = source.propsExpression?.staticType?.typeOrBound @@ -193,39 +198,34 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { return true; }); - final String? additionalSxElement; - final fixmes = []; - if (mightForwardSx) { - var canGetForwardedSx = false; - final forwardedProps = - forwardedPropSources.singleOrNull?.propsExpression; - if (forwardedProps != null && - (forwardedProps is Identifier || - forwardedProps is PropertyAccess)) { - final forwardedPropsType = - forwardedProps.staticType?.typeOrBound.tryCast(); - if (forwardedPropsType?.element - .lookUpGetter('sx', forwardedPropsType.element.library) != - null) { - canGetForwardedSx = true; - } + // Try to access the forwarded sx prop to merge it in. + bool canSafelyGetForwardedSx; + final props = forwardedPropSources.singleOrNull?.propsExpression; + if (props != null && (props is Identifier || props is PropertyAccess)) { + final propsElement = + props.staticType?.typeOrBound.tryCast()?.element; + canSafelyGetForwardedSx = + propsElement?.lookUpGetter('sx', propsElement.library) != null; + } else { + canSafelyGetForwardedSx = false; } - if (canGetForwardedSx) { - additionalSxElement = '...?${context.sourceFor(forwardedProps!)}.sx'; + if (canSafelyGetForwardedSx) { + forwardedSxSpread = '...?${context.sourceFor(props!)}.sx'; } else { - additionalSxElement = null; + forwardedSxSpread = null; fixmes.add( - 'merge in any sx prop forwarded to this component, if needed'); + 'merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior)'); } } else { - additionalSxElement = null; + forwardedSxSpread = null; } final elements = [ - if (additionalSxElement != null) additionalSxElement, + // Insert before spread, to preserve existing behavior where any forwarded sx trumped these styles. ...migratedSystemPropEntries, + if (forwardedSxSpread != null) forwardedSxSpread, ]; final maybeTrailingComma = shouldForceMultiline(elements) ? ',' : ''; @@ -246,7 +246,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { systemPropEnds.any((system) => system < firstForwardingEnd)) { fixmes.add( 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' - '\n Double-check that this new behavior is okay, and update logic as needed (e.g., merging in props.sx after these styles instead of before).'); + '\n Double-check that this new behavior is okay.'); } final fixmesSource = fixmes diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 42f210c0..8273a59b 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -77,9 +77,9 @@ void main() { expectedOutput: withHeader(/*language=dart*/ ''' content() => (Box() - ..sx = { - 'border': '1px solid black', + ..sx = { 'mt': 2, + 'border': '1px solid black', } )(); '''), @@ -100,9 +100,9 @@ void main() { expectedOutput: withHeader(/*language=dart*/ ''' content() => (Box() - ..sx = { - 'border': '1px solid black', + ..sx = { 'mt': 2, + 'border': '1px solid black', } )(); '''), @@ -121,7 +121,9 @@ void main() { expectedOutput: withHeader(/*language=dart*/ ''' content() => (Box() - ..sx = {'mt': 2} + ..sx = { + 'mt': 2, + } )(); '''), ); @@ -142,10 +144,10 @@ void main() { expectedOutput: withHeader(/*language=dart*/ ''' content() => (Box() - ..sx = { - 'backgroundColor': 'white', - 'color': 'blue', + ..sx = { 'mt': 2, + 'backgroundColor': 'white', + 'color': 'blue', } )(); '''), @@ -168,8 +170,8 @@ void main() { content(BoxProps props) => (Box() ..sx = { - ...?getSx(), 'mt': 2, + ...?getSx(), } )(); Map? getSx() => {'color': 'red'}; @@ -190,7 +192,7 @@ void main() { expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) => (Box() - ..sx = {...getSx(), 'mt': 2} + ..sx = {'mt': 2, ...getSx()} )(); Map getSx() => {'color': 'red'}; '''), @@ -211,8 +213,8 @@ void main() { content(BoxProps props) => (Box() ..sx = { - ...?getSx(), 'mt': 2, + ...?getSx(), } )(); dynamic getSx() => {'color': 'red'}; @@ -221,7 +223,7 @@ void main() { }); }); - test('does not consider forwarding with an existing sx', () async { + test('adds a fixme when there are forwarded props and an existing sx', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' content(BoxProps props) => @@ -236,8 +238,10 @@ void main() { (Box() ..addProps(props) ..sx = { - 'border': '1px solid black', + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. + // Double-check that this new behavior is okay. 'mt': 2, + 'border': '1px solid black', } )(); '''), @@ -259,7 +263,7 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) - ..sx = {...?props.sx, 'mt': 2,} + ..sx = {'mt': 2, ...?props.sx,} )(); '''), ); @@ -278,7 +282,7 @@ void main() { content(BoxProps props) => (Box() ..addAll(props) - ..sx = {...?props.sx, 'mt': 2,} + ..sx = {'mt': 2, ...?props.sx,} )(); '''), ); @@ -297,7 +301,7 @@ void main() { content(BoxProps props) => (Box() ..addProps(props.getPropsToForward()) - ..sx = {...?props.sx, 'mt': 2,} + ..sx = {'mt': 2, ...?props.sx,} )(); '''), ); @@ -316,7 +320,7 @@ void main() { content(BoxProps props) => (Box() ..modifyProps(props.addPropsToForward()) - ..sx = {...?props.sx, 'mt': 2,} + ..sx = {'mt': 2, ...?props.sx,} )(); '''), ); @@ -340,7 +344,7 @@ void main() { (Box() ..addProps(props) ..addTestId('test-id') - ..sx = {...?props.sx, 'mt': 2,} + ..sx = {'mt': 2, ...?props.sx,} )(); '''), ); @@ -362,7 +366,7 @@ void main() { (Box() ..modifyProps((_) {}) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) ..sx = { 'mt': 2 } @@ -388,7 +392,7 @@ void main() { (Box() ..addProps(copyUnconsumedProps()) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) ..sx = { 'mt': 2 } @@ -414,7 +418,7 @@ void main() { (Box() ..addAll(props) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) ..sx = { 'mt': 2 } @@ -439,7 +443,7 @@ void main() { (Box() ..addAll(props) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) ..sx = { 'mt': 2 } @@ -464,7 +468,7 @@ void main() { (Box() ..addAll(props) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) ..sx = { 'mt': 2 } @@ -492,7 +496,7 @@ void main() { ..addProps(props) ..addProps(props2) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) ..sx = { 'mt': 2 } @@ -648,8 +652,8 @@ void main() { (Box() ..addProps(props) // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay, and update logic as needed (e.g., merging in props.sx after these styles instead of before). - ..sx = {...?props.sx, 'mt': 2,} + // Double-check that this new behavior is okay. + ..sx = {'mt': 2, ...?props.sx,} ..id = 'test' )(); '''), @@ -696,8 +700,8 @@ void main() { ..addProps(props) // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay, and update logic as needed (e.g., merging in props.sx after these styles instead of before). - ..sx = {...?props.sx, 'mt': 2, 'p': 3,} + // Double-check that this new behavior is okay. + ..sx = {'mt': 2, 'p': 3, ...?props.sx,} )(); '''), ); @@ -719,7 +723,7 @@ void main() { (Box() ..addProps(props1) ..addProps(props2) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component, if needed + // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) ..sx = {'mt': 2, 'p': 3} )(); '''), @@ -898,13 +902,14 @@ void main() { ..mt = 2 )(); '''), + // Not sure why dartfmt allows two entries like this with trailing commas + // on the same line, but it is what it is. expectedOutput: withHeader(/*language=dart*/ ''' content() => (Box() ..sx = { - 'border': '1px solid black', // Add margin - 'mt': 2, + 'mt': 2, 'border': '1px solid black', } )(); '''), @@ -950,14 +955,15 @@ void main() { ..mt = 2 )(); '''), + // Not sure why dartfmt allows two entries like this with trailing commas + // on the same line, but it is what it is. expectedOutput: withHeader(/*language=dart*/ ''' content(BoxProps props) => (Box() ..addProps(props) ..sx = { - ...?props.sx, // Override margin - 'mt': 2, + 'mt': 2, ...?props.sx, } )(); '''), From 34738b5c90fdd2d836d7d296f475aa1a06d3cdb4 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 14:55:20 -0700 Subject: [PATCH 16/43] Add fixmes in all cases where behavior changes --- .../system_props_to_sx_migrator.dart | 49 +++++---- .../system_props_to_sx_migrator_test.dart | 104 +++++++++++++----- 2 files changed, 106 insertions(+), 47 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 6e38d90d..4285637d 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -133,13 +133,20 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { prop.node.end); } - bool shouldForceMultiline(List mapElements) => - // Force multiline if there are a certain number of entries, or... - mapElements.length >= 3 || - // there's more than one entry and the line is getting too long, or... - (mapElements.length > 1 && mapElements.join(', ').length >= 20) || - // any entry is multiline (including comments). - mapElements.any((e) => e.contains('\n')); + final forwardedPropSources = _getForwardedPropSources(usage); + + + final fixmes = []; + if (forwardedPropSources.isNotEmpty) { + fixmes.add( + 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' + '\n Double-check that this new behavior is okay.'); + } + + String getFixmesSource() => fixmes + // Indents with a single space so that dartfmt doesn't make it stick to the beginning of the line. + .map((f) => '\n' + lineComment('$fixmePrefix - $f', indent: ' ')) + .join(''); if (existingSxProp != null) { // @@ -151,7 +158,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { // Insert before, to preserve existing behavior where any spread sx trumped these styles. // Add a leading newline to ensure comments don't get stuck to the opening braces. - yieldPatch('\n${migratedSystemPropEntries.join(',\n')},', + yieldPatch('${getFixmesSource()}\n${migratedSystemPropEntries.join(',\n')},', value.leftBracket.end, value.leftBracket.end); // Force a multiline in all cases by ensuring there's a trailing comma. final hadTrailingComma = @@ -169,9 +176,9 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { type.nullabilitySuffix == NullabilitySuffix.none; final spread = '...${nonNullable ? '' : '?'}'; // Insert before spread, to preserve existing behavior where any forwarded sx trumped these styles. - yieldPatch('{\n${migratedSystemPropEntries.join(', ')}, $spread', + yieldPatch('{${getFixmesSource()}\n${migratedSystemPropEntries.join(', ')}, $spread', value.offset, value.offset); - final maybeTrailingComma = shouldForceMultiline([ + final maybeTrailingComma = _shouldForceMultiline([ ...migratedSystemPropEntries, '$spread${context.sourceFor(value)}', ]) @@ -184,9 +191,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { // Case 2: add new sx prop assignment final String? forwardedSxSpread; - final fixmes = []; - final forwardedPropSources = _getForwardedPropSources(usage); final mightForwardSx = forwardedPropSources.any((source) { final type = source.propsExpression?.staticType?.typeOrBound .tryCast(); @@ -227,7 +232,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { ...migratedSystemPropEntries, if (forwardedSxSpread != null) forwardedSxSpread, ]; - final maybeTrailingComma = shouldForceMultiline(elements) ? ',' : ''; + final maybeTrailingComma = _shouldForceMultiline(elements) ? ',' : ''; final forwardingEnds = forwardedPropSources.map((s) => s.cascadedMethod.node.end).toList(); @@ -244,21 +249,23 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (firstForwardingEnd != null && insertionLocation >= firstForwardingEnd && systemPropEnds.any((system) => system < firstForwardingEnd)) { - fixmes.add( - 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' - '\n Double-check that this new behavior is okay.'); } - final fixmesSource = fixmes - // Indents with a single space so that dartfmt doesn't make it stick to the beginning of the line. - .map((f) => '\n' + lineComment('$fixmePrefix - $f', indent: ' ')) - .join(''); yieldPatch( - '$fixmesSource..sx = {${elements.join(', ')}$maybeTrailingComma}', + '${getFixmesSource()}..sx = {${elements.join(', ')}$maybeTrailingComma}', insertionLocation, insertionLocation); } } + + static bool _shouldForceMultiline(List mapElements) => + // Force multiline if there are a certain number of entries, or... + mapElements.length >= 3 || + // there's more than one entry and the line is getting too long, or... + (mapElements.length > 1 && mapElements.join(', ').length >= 20) || + // any entry is multiline (including comments). + mapElements.any((e) => e.contains('\n')); + } enum CommentLocation { ownLine, endOfLine, other } diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 8273a59b..6f100b37 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -223,31 +223,6 @@ void main() { }); }); - test('adds a fixme when there are forwarded props and an existing sx', () async { - await testSuggestor( - input: withHeader(/*language=dart*/ ''' - content(BoxProps props) => - (Box() - ..addProps(props) - ..sx = {'border': '1px solid black'} - ..mt = 2 - )(); - '''), - expectedOutput: withHeader(/*language=dart*/ ''' - content(BoxProps props) => - (Box() - ..addProps(props) - ..sx = { - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay. - 'mt': 2, - 'border': '1px solid black', - } - )(); - '''), - ); - }); - group('merges in sx from forwarded props', () { group('forwarded with', () { test('addProps', () async { @@ -634,10 +609,87 @@ void main() { ); }); + group('adds a fixme when there are forwarded props and', () { + test('an existing sx map literal', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = {'border': '1px solid black'} + ..mt = 2 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = { + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. + // Double-check that this new behavior is okay. + + 'mt': 2, 'border': '1px solid black', + } + )(); + '''), + ); + }); + + test('an existing sx value', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = getSx() + ..mt = 2 + )(); + Map getSx() => {'color': 'red'}; + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + ..sx = { + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. + // Double-check that this new behavior is okay. + + 'mt': 2, ...getSx() + } + )(); + Map getSx() => {'color': 'red'}; + '''), + ); + }); + test('no existing sx', () async { + await testSuggestor( + input: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + ..mt = 2 + )(); + '''), + expectedOutput: withHeader(/*language=dart*/ ''' + content(BoxProps props) => + (Box() + ..addProps(props) + + // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. + // Double-check that this new behavior is okay. + ..sx = { + 'mt': 2, + ...?props.sx, + } + )(); + '''), + ); + }); + }); group('insertion location:', () { - test('inserts after prop forwarding to avoid being overwritten, with a comment', () async { + test('inserts after prop forwarding to avoid being overwritten', () async { await testSuggestor( input: withHeader(/*language=dart*/ ''' content(BoxProps props) => From b69b2d745e643896752c59e341be3857683bb266 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 14:58:10 -0700 Subject: [PATCH 17/43] Remove language comments since interpolation messes them up --- .../system_props_to_sx_migrator_test.dart | 196 +++++++++--------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 6f100b37..ee80cd7b 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -30,11 +30,11 @@ void main() { test('migrates single system prop to sx', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box()..mt = 2)(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box()..sx = {'mt': 2})(); '''), @@ -43,7 +43,7 @@ void main() { test('migrates multiple system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..mt = 2 @@ -51,7 +51,7 @@ void main() { ..bgcolor = 'primary.main' )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -67,14 +67,14 @@ void main() { group('merges with existing sx map literal', () { test('without trailing commas', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..sx = {'border': '1px solid black'} ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -88,7 +88,7 @@ void main() { test('with trailing commas', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..sx = { @@ -97,7 +97,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -111,14 +111,14 @@ void main() { test('that is empty', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..sx = {} ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -131,7 +131,7 @@ void main() { test('with multiple existing entries', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..sx = { @@ -141,7 +141,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -158,7 +158,7 @@ void main() { group('merges with forwarded sx prop using spread:', () { test('nullable', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..sx = getSx() @@ -166,7 +166,7 @@ void main() { )(); Map? getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..sx = { @@ -181,7 +181,7 @@ void main() { test('non-nullable', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..sx = getSx() @@ -189,7 +189,7 @@ void main() { )(); Map getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..sx = {'mt': 2, ...getSx()} @@ -201,7 +201,7 @@ void main() { test('dynamic', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..sx = getSx() @@ -209,7 +209,7 @@ void main() { )(); dynamic getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..sx = { @@ -227,14 +227,14 @@ void main() { group('forwarded with', () { test('addProps', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -246,14 +246,14 @@ void main() { test('addAll', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addAll(props) ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addAll(props) @@ -265,14 +265,14 @@ void main() { test('getPropsToForward', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addProps(props.getPropsToForward()) ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props.getPropsToForward()) @@ -284,14 +284,14 @@ void main() { test('addPropsToForward', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..modifyProps(props.addPropsToForward()) ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..modifyProps(props.addPropsToForward()) @@ -306,7 +306,7 @@ void main() { test('even when there are other unrelated calls in the cascade', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -314,7 +314,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -329,14 +329,14 @@ void main() { group('adds FIXME comment when forwarding is ambiguous:', () { test('modifyProps with unknown function', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..modifyProps((_) {}) ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..modifyProps((_) {}) @@ -352,7 +352,7 @@ void main() { test('copyUnconsumedProps', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' abstract class FooComponent extends UiComponent2 { content(BoxProps props) => (Box() @@ -361,7 +361,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' abstract class FooComponent extends UiComponent2 { content(BoxProps props) => (Box() @@ -380,7 +380,7 @@ void main() { group('generic props:', () { test('Map', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(Map props) { (Box() ..addAll(props) @@ -388,7 +388,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(Map props) { (Box() ..addAll(props) @@ -405,7 +405,7 @@ void main() { test('UiProps', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(UiProps props) { (Box() ..addAll(props) @@ -413,7 +413,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(UiProps props) { (Box() ..addAll(props) @@ -430,7 +430,7 @@ void main() { test('dynamic', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(dynamic props) { (Box() ..addAll(props) @@ -438,7 +438,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(dynamic props) { (Box() ..addAll(props) @@ -456,7 +456,7 @@ void main() { test('multiple prop forwarding calls', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props, BoxProps props2) { (Box() ..addProps(props) @@ -465,7 +465,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props, BoxProps props2) { (Box() ..addProps(props) @@ -484,7 +484,7 @@ void main() { test('handles complex prop values with expressions', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(bool condition) { (Box() ..mt = condition ? 2 : 4 @@ -493,7 +493,7 @@ void main() { } int getSpacing() => 3; '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(bool condition) { (Box() ..sx = { @@ -509,13 +509,13 @@ void main() { test('handles responsive system prop values', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..mt = {'xs': 1, 'sm': 2, 'md': 3} )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = {'mt': {'xs': 1, 'sm': 2, 'md': 3}} @@ -526,7 +526,7 @@ void main() { test('preserves non-system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..id = 'test' @@ -535,7 +535,7 @@ void main() { ..onClick = (_) {} )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..id = 'test' @@ -549,13 +549,13 @@ void main() { test('handles multiple components in the same file', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() { (Box()..mt = 2)(); (Box()..p = 3)(); } '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() { (Box()..sx = {'mt': 2})(); (Box()..sx = {'p': 3})(); @@ -566,7 +566,7 @@ void main() { test('respects orcm_ignore comments', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' // orcm_ignore content() => (Box()..mt = 2)(); '''), @@ -576,7 +576,7 @@ void main() { test('does not migrate components without deprecated system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..id = 'test' @@ -589,7 +589,7 @@ void main() { test('does not migrate deprecated props with the same name as system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (TextField()..color = '')(); '''), ); @@ -597,7 +597,7 @@ void main() { test('does not flag unrelated cascades with FIXMEs', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..addProp('foo', 'bar') @@ -612,7 +612,7 @@ void main() { group('adds a fixme when there are forwarded props and', () { test('an existing sx map literal', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -620,7 +620,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -637,7 +637,7 @@ void main() { test('an existing sx value', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -646,7 +646,7 @@ void main() { )(); Map getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -664,14 +664,14 @@ void main() { test('no existing sx', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -691,7 +691,7 @@ void main() { group('insertion location:', () { test('inserts after prop forwarding to avoid being overwritten', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..mt = 2 @@ -699,7 +699,7 @@ void main() { ..id = 'test' )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -714,7 +714,7 @@ void main() { test('inserts at location of last system prop when no prop forwarding', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..id = 'first' @@ -724,7 +724,7 @@ void main() { ..onClick = (_) {} )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..id = 'first' @@ -738,7 +738,7 @@ void main() { test('inserts after latest of (forwarding or last system prop)', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..mt = 2 @@ -746,7 +746,7 @@ void main() { ..p = 3 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -761,7 +761,7 @@ void main() { test('inserts after all forwarding calls when multiple exist', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props1, BoxProps props2) => (Box() ..addProps(props1) @@ -770,7 +770,7 @@ void main() { ..addProps(props2) )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props1, BoxProps props2) => (Box() ..addProps(props1) @@ -786,14 +786,14 @@ void main() { group('multiline formatting:', () { test('uses single line for short sx maps (< 3 elements)', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..mt = 2 ..p = 3 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = {'mt': 2, 'p': 3} @@ -804,7 +804,7 @@ void main() { test('uses multiline for 3+ elements', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..mt = 2 @@ -812,7 +812,7 @@ void main() { ..mb = 4 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -827,14 +827,14 @@ void main() { test('uses multiline for long content (>= 20 chars with 2+ elements)', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..mt = 2 ..bgcolor = 'verylongcolor.main' )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -850,14 +850,14 @@ void main() { group('preserves comments before system props:', () { test('single line comment before single system prop', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() // Add margin ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -871,7 +871,7 @@ void main() { test('single line comment before one of multiple system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..mt = 2 @@ -879,7 +879,7 @@ void main() { ..p = 3 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -894,7 +894,7 @@ void main() { test('multiple comments before different system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() // Top margin @@ -905,7 +905,7 @@ void main() { ..bgcolor = 'blue' )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -923,7 +923,7 @@ void main() { test('multi-line comment before system prop', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() /* This is a longer comment @@ -931,7 +931,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -946,7 +946,7 @@ void main() { test('comment before system prop with existing sx', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..sx = {'border': '1px solid black'} @@ -956,7 +956,7 @@ void main() { '''), // Not sure why dartfmt allows two entries like this with trailing commas // on the same line, but it is what it is. - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..sx = { @@ -970,7 +970,7 @@ void main() { test('comments before system props mixed with non-system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..id = 'test' @@ -981,7 +981,7 @@ void main() { ..p = 3 )(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() ..id = 'test' @@ -999,7 +999,7 @@ void main() { test('comment before system prop with forwarding', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -1009,7 +1009,7 @@ void main() { '''), // Not sure why dartfmt allows two entries like this with trailing commas // on the same line, but it is what it is. - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) @@ -1024,7 +1024,7 @@ void main() { test('inline comment on same line as system prop', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() ..mt = 2 // top margin @@ -1034,7 +1034,7 @@ void main() { // Inline comment behavior here isn't great, but it's too much effort // to deal with in this codemod. // Just verify the codemod doesn't break or do anything too outlandish. - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() // top margin @@ -1046,7 +1046,7 @@ void main() { test('multiple comment types with system props', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box() // Line comment @@ -1058,7 +1058,7 @@ void main() { // Inline comment behavior here isn't great, but it's too much effort // to deal with in this codemod. // Just verify the codemod doesn't break or do anything too outlandish. - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box() // inline comment @@ -1077,10 +1077,10 @@ void main() { group('handles different component types with system props:', () { test('Box', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Box()..mt = 2)(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Box()..sx = {'mt': 2})(); '''), ); @@ -1088,10 +1088,10 @@ void main() { test('Grid', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Grid()..mt = 2)(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Grid()..sx = {'mt': 2})(); '''), ); @@ -1099,10 +1099,10 @@ void main() { test('Stack', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Stack()..mt = 2)(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Stack()..sx = {'mt': 2})(); '''), ); @@ -1110,10 +1110,10 @@ void main() { test('Typography', () async { await testSuggestor( - input: withHeader(/*language=dart*/ ''' + input: withHeader(''' content() => (Typography()..mt = 2)(); '''), - expectedOutput: withHeader(/*language=dart*/ ''' + expectedOutput: withHeader(''' content() => (Typography()..sx = {'mt': 2})(); '''), ); From 32aef9d96487bfdcd75f43df4a0212a529318985 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 14:58:20 -0700 Subject: [PATCH 18/43] Format --- .../system_props_to_sx_migrator_test.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index ee80cd7b..9348695e 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -689,7 +689,8 @@ void main() { }); group('insertion location:', () { - test('inserts after prop forwarding to avoid being overwritten', () async { + test('inserts after prop forwarding to avoid being overwritten', + () async { await testSuggestor( input: withHeader(''' content(BoxProps props) => @@ -712,7 +713,8 @@ void main() { ); }); - test('inserts at location of last system prop when no prop forwarding', () async { + test('inserts at location of last system prop when no prop forwarding', + () async { await testSuggestor( input: withHeader(''' content() => @@ -736,7 +738,8 @@ void main() { ); }); - test('inserts after latest of (forwarding or last system prop)', () async { + test('inserts after latest of (forwarding or last system prop)', + () async { await testSuggestor( input: withHeader(''' content(BoxProps props) => @@ -825,7 +828,8 @@ void main() { ); }); - test('uses multiline for long content (>= 20 chars with 2+ elements)', () async { + test('uses multiline for long content (>= 20 chars with 2+ elements)', + () async { await testSuggestor( input: withHeader(''' content() => @@ -968,7 +972,8 @@ void main() { ); }); - test('comments before system props mixed with non-system props', () async { + test('comments before system props mixed with non-system props', + () async { await testSuggestor( input: withHeader(''' content() => From b0225be4c81dfe65e35b8501818e13d5208694e6 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 15:00:46 -0700 Subject: [PATCH 19/43] Use consts for FIXMEs --- .../system_props_to_sx_migrator_test.dart | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 9348695e..70b045af 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -28,6 +28,13 @@ void main() { resolvedContext: resolvedContext, ); + const sxPrecedenceFixme = + '// FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' + '\n // Double-check that this new behavior is okay.'; + + const sxMergeFixme = + '// FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior)'; + test('migrates single system prop to sx', () async { await testSuggestor( input: withHeader(''' @@ -341,7 +348,7 @@ void main() { (Box() ..modifyProps((_) {}) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) + $sxMergeFixme ..sx = { 'mt': 2 } @@ -367,7 +374,7 @@ void main() { (Box() ..addProps(copyUnconsumedProps()) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) + $sxMergeFixme ..sx = { 'mt': 2 } @@ -393,7 +400,7 @@ void main() { (Box() ..addAll(props) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) + $sxMergeFixme ..sx = { 'mt': 2 } @@ -418,7 +425,7 @@ void main() { (Box() ..addAll(props) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) + $sxMergeFixme ..sx = { 'mt': 2 } @@ -443,7 +450,7 @@ void main() { (Box() ..addAll(props) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) + $sxMergeFixme ..sx = { 'mt': 2 } @@ -471,7 +478,7 @@ void main() { ..addProps(props) ..addProps(props2) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) + $sxMergeFixme ..sx = { 'mt': 2 } @@ -625,8 +632,7 @@ void main() { (Box() ..addProps(props) ..sx = { - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay. + $sxPrecedenceFixme 'mt': 2, 'border': '1px solid black', } @@ -651,8 +657,7 @@ void main() { (Box() ..addProps(props) ..sx = { - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay. + $sxPrecedenceFixme 'mt': 2, ...getSx() } @@ -676,8 +681,7 @@ void main() { (Box() ..addProps(props) - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay. + $sxPrecedenceFixme ..sx = { 'mt': 2, ...?props.sx, @@ -704,8 +708,7 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay. + $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} ..id = 'test' )(); @@ -754,8 +757,7 @@ void main() { (Box() ..addProps(props) - // FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence. - // Double-check that this new behavior is okay. + $sxPrecedenceFixme ..sx = {'mt': 2, 'p': 3, ...?props.sx,} )(); '''), @@ -778,7 +780,7 @@ void main() { (Box() ..addProps(props1) ..addProps(props2) - // FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior) + $sxMergeFixme ..sx = {'mt': 2, 'p': 3} )(); '''), From 9a47c130cd8a636a4f3da68ccfabdf2d0d7fe823 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 15:06:40 -0700 Subject: [PATCH 20/43] Fix tests, remove extra newline --- .../system_props_to_sx_migrator.dart | 13 +++++++++---- .../system_props_to_sx_migrator_test.dart | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 4285637d..6bb53f45 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -143,10 +143,15 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { '\n Double-check that this new behavior is okay.'); } - String getFixmesSource() => fixmes - // Indents with a single space so that dartfmt doesn't make it stick to the beginning of the line. - .map((f) => '\n' + lineComment('$fixmePrefix - $f', indent: ' ')) - .join(''); + String getFixmesSource() { + if (fixmes.isEmpty) return ''; + // Add a leading newline to ensure comments don't get stuck to the previous line. + return '\n' + + fixmes + // Indent with a single space so that dartfmt doesn't make it stick to the beginning of the line. + .map((f) => lineComment('$fixmePrefix - $f', indent: ' ')) + .join(''); + } if (existingSxProp != null) { // diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 70b045af..32ac9861 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -245,6 +245,8 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) + + $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -264,6 +266,8 @@ void main() { content(BoxProps props) => (Box() ..addAll(props) + + $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -283,6 +287,8 @@ void main() { content(BoxProps props) => (Box() ..addProps(props.getPropsToForward()) + + $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -302,6 +308,8 @@ void main() { content(BoxProps props) => (Box() ..modifyProps(props.addPropsToForward()) + + $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -326,6 +334,8 @@ void main() { (Box() ..addProps(props) ..addTestId('test-id') + + $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -348,6 +358,7 @@ void main() { (Box() ..modifyProps((_) {}) + $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -374,6 +385,7 @@ void main() { (Box() ..addProps(copyUnconsumedProps()) + $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -400,6 +412,7 @@ void main() { (Box() ..addAll(props) + $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -425,6 +438,7 @@ void main() { (Box() ..addAll(props) + $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -450,6 +464,7 @@ void main() { (Box() ..addAll(props) + $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -478,6 +493,7 @@ void main() { ..addProps(props) ..addProps(props2) + $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -780,6 +796,7 @@ void main() { (Box() ..addProps(props1) ..addProps(props2) + $sxPrecedenceFixme $sxMergeFixme ..sx = {'mt': 2, 'p': 3} )(); @@ -1020,6 +1037,8 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) + + $sxPrecedenceFixme ..sx = { // Override margin 'mt': 2, ...?props.sx, From c6e10525d8ead80477ee6326361243e737451288 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 15:07:27 -0700 Subject: [PATCH 21/43] Format --- .../system_props_to_sx_migrator.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 6bb53f45..9b36ebf4 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -135,12 +135,11 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final forwardedPropSources = _getForwardedPropSources(usage); - final fixmes = []; if (forwardedPropSources.isNotEmpty) { fixmes.add( 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' - '\n Double-check that this new behavior is okay.'); + '\n Double-check that this new behavior is okay.'); } String getFixmesSource() { @@ -163,8 +162,10 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { // Insert before, to preserve existing behavior where any spread sx trumped these styles. // Add a leading newline to ensure comments don't get stuck to the opening braces. - yieldPatch('${getFixmesSource()}\n${migratedSystemPropEntries.join(',\n')},', - value.leftBracket.end, value.leftBracket.end); + yieldPatch( + '${getFixmesSource()}\n${migratedSystemPropEntries.join(',\n')},', + value.leftBracket.end, + value.leftBracket.end); // Force a multiline in all cases by ensuring there's a trailing comma. final hadTrailingComma = value.rightBracket.previous?.type == TokenType.COMMA; @@ -181,8 +182,10 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { type.nullabilitySuffix == NullabilitySuffix.none; final spread = '...${nonNullable ? '' : '?'}'; // Insert before spread, to preserve existing behavior where any forwarded sx trumped these styles. - yieldPatch('{${getFixmesSource()}\n${migratedSystemPropEntries.join(', ')}, $spread', - value.offset, value.offset); + yieldPatch( + '{${getFixmesSource()}\n${migratedSystemPropEntries.join(', ')}, $spread', + value.offset, + value.offset); final maybeTrailingComma = _shouldForceMultiline([ ...migratedSystemPropEntries, '$spread${context.sourceFor(value)}', @@ -253,8 +256,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final firstForwardingEnd = forwardingEnds.minOrNull; if (firstForwardingEnd != null && insertionLocation >= firstForwardingEnd && - systemPropEnds.any((system) => system < firstForwardingEnd)) { - } + systemPropEnds.any((system) => system < firstForwardingEnd)) {} yieldPatch( '${getFixmesSource()}..sx = {${elements.join(', ')}$maybeTrailingComma}', @@ -270,7 +272,6 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { (mapElements.length > 1 && mapElements.join(', ').length >= 20) || // any entry is multiline (including comments). mapElements.any((e) => e.contains('\n')); - } enum CommentLocation { ownLine, endOfLine, other } From 43228a2b71aec06f6b1e627f28cfbb29ab248016 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 15:09:16 -0700 Subject: [PATCH 22/43] Clean up old code, simplify --- .../system_props_to_sx_migrator.dart | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 9b36ebf4..3d3e2db2 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -242,21 +242,13 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { ]; final maybeTrailingComma = _shouldForceMultiline(elements) ? ',' : ''; - final forwardingEnds = - forwardedPropSources.map((s) => s.cascadedMethod.node.end).toList(); - final systemPropEnds = - deprecatedSystemProps.map((p) => p.node.end).toList(); - // Insert after any forwarded props to ensure sx isn't overwritten, - // and where the last system prop used to be to preserve the location and reduce diffs. - final insertionLocation = [...forwardingEnds, ...systemPropEnds].max; - - // Add a note to check for potential behavior changes when system props - // used to come before prop forwarding, but now get added in sx after. - final firstForwardingEnd = forwardingEnds.minOrNull; - if (firstForwardingEnd != null && - insertionLocation >= firstForwardingEnd && - systemPropEnds.any((system) => system < firstForwardingEnd)) {} + // or where the last system prop used to be to preserve the location and reduce diffs, + // whichever is later. + final insertionLocation = [ + ...forwardedPropSources.map((s) => s.cascadedMethod.node.end), + ...deprecatedSystemProps.map((p) => p.node.end) + ].max; yieldPatch( '${getFixmesSource()}..sx = {${elements.join(', ')}$maybeTrailingComma}', From bdd35a47007ea2270bc494a0e8a94fa0c78b33c8 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 15:39:38 -0700 Subject: [PATCH 23/43] Only apply precedence fixme when needed, update verbiage --- .../system_props_to_sx_migrator.dart | 16 ++++++-- .../system_props_to_sx_migrator_test.dart | 39 ++++++------------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 3d3e2db2..e7780940 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -137,9 +137,17 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final fixmes = []; if (forwardedPropSources.isNotEmpty) { - fixmes.add( - 'Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' - '\n Double-check that this new behavior is okay.'); + final propForwardingOffsets = + forwardedPropSources.map((s) => s.cascadedMethod.node.end).toList(); + final systemPropOffsets = + deprecatedSystemProps.map((p) => p.node.end).toList(); + final anySystemPropSetBeforeForwarding = + systemPropOffsets.min < propForwardingOffsets.max; + if (anySystemPropSetBeforeForwarding) { + fixmes.add( + 'Previously, it was possible for forwarded system props to overwrite these styles, but not anymore since sx takes precedence over system props.' + '\n Double-check that this new behavior is okay.'); + } } String getFixmesSource() { @@ -229,7 +237,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { } else { forwardedSxSpread = null; fixmes.add( - 'merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior)'); + 'spread in any sx prop forwarded to this component above, if needed (spread should go after these new styles to preserve behavior)'); } } else { forwardedSxSpread = null; diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 32ac9861..c9220792 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -29,11 +29,11 @@ void main() { ); const sxPrecedenceFixme = - '// FIXME(mui_system_props_migration) - Some of these system props used to be able to be overwritten by prop forwarding, but not anymore since sx takes precedence.' + '// FIXME(mui_system_props_migration) - Previously, it was possible for forwarded system props to overwrite these styles, but not anymore since sx takes precedence over system props.' '\n // Double-check that this new behavior is okay.'; const sxMergeFixme = - '// FIXME(mui_system_props_migration) - merge in any sx prop forwarded to this component if needed (after these new styles to preserve behavior)'; + '// FIXME(mui_system_props_migration) - spread in any sx prop forwarded to this component above, if needed (spread should go after these new styles to preserve behavior)'; test('migrates single system prop to sx', () async { await testSuggestor( @@ -245,8 +245,6 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) - - $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -266,8 +264,6 @@ void main() { content(BoxProps props) => (Box() ..addAll(props) - - $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -287,8 +283,6 @@ void main() { content(BoxProps props) => (Box() ..addProps(props.getPropsToForward()) - - $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -308,8 +302,6 @@ void main() { content(BoxProps props) => (Box() ..modifyProps(props.addPropsToForward()) - - $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -334,8 +326,6 @@ void main() { (Box() ..addProps(props) ..addTestId('test-id') - - $sxPrecedenceFixme ..sx = {'mt': 2, ...?props.sx,} )(); '''), @@ -358,7 +348,6 @@ void main() { (Box() ..modifyProps((_) {}) - $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -385,7 +374,6 @@ void main() { (Box() ..addProps(copyUnconsumedProps()) - $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -411,8 +399,7 @@ void main() { content(Map props) { (Box() ..addAll(props) - - $sxPrecedenceFixme + $sxMergeFixme ..sx = { 'mt': 2 @@ -437,8 +424,7 @@ void main() { content(UiProps props) { (Box() ..addAll(props) - - $sxPrecedenceFixme + $sxMergeFixme ..sx = { 'mt': 2 @@ -463,8 +449,7 @@ void main() { content(dynamic props) { (Box() ..addAll(props) - - $sxPrecedenceFixme + $sxMergeFixme ..sx = { 'mt': 2 @@ -493,7 +478,6 @@ void main() { ..addProps(props) ..addProps(props2) - $sxPrecedenceFixme $sxMergeFixme ..sx = { 'mt': 2 @@ -632,15 +616,16 @@ void main() { ); }); - group('adds a fixme when there are forwarded props and', () { + group('adds a fixme when there are forwarded props after system props and', + () { test('an existing sx map literal', () async { await testSuggestor( input: withHeader(''' content(BoxProps props) => (Box() + ..mt = 2 ..addProps(props) ..sx = {'border': '1px solid black'} - ..mt = 2 )(); '''), expectedOutput: withHeader(''' @@ -662,9 +647,9 @@ void main() { input: withHeader(''' content(BoxProps props) => (Box() + ..mt = 2 ..addProps(props) ..sx = getSx() - ..mt = 2 )(); Map getSx() => {'color': 'red'}; '''), @@ -688,15 +673,14 @@ void main() { input: withHeader(''' content(BoxProps props) => (Box() - ..addProps(props) ..mt = 2 + ..addProps(props) )(); '''), expectedOutput: withHeader(''' content(BoxProps props) => (Box() ..addProps(props) - $sxPrecedenceFixme ..sx = { 'mt': 2, @@ -1037,8 +1021,7 @@ void main() { content(BoxProps props) => (Box() ..addProps(props) - - $sxPrecedenceFixme + ..sx = { // Override margin 'mt': 2, ...?props.sx, From 07558e62b57ff3b768ec47d9bf46a1682568d0cb Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 15:42:33 -0700 Subject: [PATCH 24/43] Clean up names, system prop detection logic --- .../system_props_to_sx_migrator.dart | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index e7780940..0f93fe5b 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -77,7 +77,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { void migrateUsage(FluentComponentUsage usage) { super.migrateUsage(usage); - final deprecatedSystemProps = []; + final systemProps = []; PropAssignment? existingSxProp; // Identify system props and existing sx prop @@ -86,24 +86,21 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (propName == 'sx') { existingSxProp = prop; } else if (_systemPropNames.contains(propName)) { - final propElement = prop.staticElement?.nonSynthetic; - final enclosingElement = propElement?.enclosingElement; - late final componentName = - enclosingElement?.name?.replaceAll(RegExp(r'Props(Mixin)?$'), ''); - if (propElement != null && - propElement.hasDeprecated && - enclosingElement != null && + final propClassElement = prop.staticElement?.enclosingElement; + final componentName = + propClassElement?.name?.replaceAll(RegExp(r'Props(Mixin)?$'), ''); + if (propClassElement != null && _componentsWithDeprecatedSystemProps.contains(componentName) && - enclosingElement.isDeclaredInPackage('unify_ui')) { - deprecatedSystemProps.add(prop); + propClassElement.isDeclaredInPackage('unify_ui')) { + systemProps.add(prop); } } } - if (deprecatedSystemProps.isEmpty) return; + if (systemProps.isEmpty) return; final migratedSystemPropEntries = []; - for (final prop in deprecatedSystemProps) { + for (final prop in systemProps) { final propName = prop.name.name; final propValue = context.sourceFile .getText(prop.rightHandSide.offset, prop.rightHandSide.end); @@ -133,14 +130,13 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { prop.node.end); } - final forwardedPropSources = _getForwardedPropSources(usage); + final propForwardingSources = _getPropForwardingSources(usage); final fixmes = []; - if (forwardedPropSources.isNotEmpty) { + if (propForwardingSources.isNotEmpty) { final propForwardingOffsets = - forwardedPropSources.map((s) => s.cascadedMethod.node.end).toList(); - final systemPropOffsets = - deprecatedSystemProps.map((p) => p.node.end).toList(); + propForwardingSources.map((s) => s.cascadedMethod.node.end).toList(); + final systemPropOffsets = systemProps.map((p) => p.node.end).toList(); final anySystemPropSetBeforeForwarding = systemPropOffsets.min < propForwardingOffsets.max; if (anySystemPropSetBeforeForwarding) { @@ -208,7 +204,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final String? forwardedSxSpread; - final mightForwardSx = forwardedPropSources.any((source) { + final mightForwardSx = propForwardingSources.any((source) { final type = source.propsExpression?.staticType?.typeOrBound .tryCast(); if (type != null && @@ -222,7 +218,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (mightForwardSx) { // Try to access the forwarded sx prop to merge it in. bool canSafelyGetForwardedSx; - final props = forwardedPropSources.singleOrNull?.propsExpression; + final props = propForwardingSources.singleOrNull?.propsExpression; if (props != null && (props is Identifier || props is PropertyAccess)) { final propsElement = props.staticType?.typeOrBound.tryCast()?.element; @@ -254,8 +250,8 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { // or where the last system prop used to be to preserve the location and reduce diffs, // whichever is later. final insertionLocation = [ - ...forwardedPropSources.map((s) => s.cascadedMethod.node.end), - ...deprecatedSystemProps.map((p) => p.node.end) + ...propForwardingSources.map((s) => s.cascadedMethod.node.end), + ...systemProps.map((p) => p.node.end) ].max; yieldPatch( @@ -276,16 +272,16 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { enum CommentLocation { ownLine, endOfLine, other } -class _ForwardedPropSource { +class _PropForwardingSource { final BuilderMethodInvocation cascadedMethod; final Expression? propsExpression; - _ForwardedPropSource(this.cascadedMethod, this.propsExpression); + _PropForwardingSource(this.cascadedMethod, this.propsExpression); } /// Returns the set of expressions that are sources of props forwarded to the component in [usage], /// or `null` for sources that were detected but don't cleanly map to a props expression. -List<_ForwardedPropSource> _getForwardedPropSources( +List<_PropForwardingSource> _getPropForwardingSources( FluentComponentUsage usage) { return usage.cascadedMethodInvocations .map((c) { @@ -294,23 +290,23 @@ List<_ForwardedPropSource> _getForwardedPropSources( switch (methodName) { case 'addUnconsumedProps': - return _ForwardedPropSource(c, arg); + return _PropForwardingSource(c, arg); case 'addAll': case 'addProps': if (arg is MethodInvocation && arg.methodName.name == 'getPropsToForward') { - return _ForwardedPropSource(c, arg.realTarget); + return _PropForwardingSource(c, arg.realTarget); } - return _ForwardedPropSource(c, arg); + return _PropForwardingSource(c, arg); case 'modifyProps': if ((arg is MethodInvocation && arg.methodName.name == 'addPropsToForward')) { - return _ForwardedPropSource(c, arg.realTarget); + return _PropForwardingSource(c, arg.realTarget); } if (arg is Identifier && arg.name == 'addUnconsumedProps') { - return _ForwardedPropSource(c, null); + return _PropForwardingSource(c, null); } - return _ForwardedPropSource(c, null); + return _PropForwardingSource(c, null); default: // Not a method that forwards props. return null; From 78caea214cb307de60666f5db516f85601f57916 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 15:49:05 -0700 Subject: [PATCH 25/43] Improve verbiage --- lib/src/mui_suggestors/system_props_to_sx_migrator.dart | 4 ++-- test/mui_suggestors/system_props_to_sx_migrator_test.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 0f93fe5b..c5987048 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -141,7 +141,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { systemPropOffsets.min < propForwardingOffsets.max; if (anySystemPropSetBeforeForwarding) { fixmes.add( - 'Previously, it was possible for forwarded system props to overwrite these styles, but not anymore since sx takes precedence over system props.' + 'Previously, it was possible for forwarded system props to overwrite these migrated styles, but not anymore since sx takes precedence over any system props.' '\n Double-check that this new behavior is okay.'); } } @@ -233,7 +233,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { } else { forwardedSxSpread = null; fixmes.add( - 'spread in any sx prop forwarded to this component above, if needed (spread should go after these new styles to preserve behavior)'); + 'spread in any sx prop forwarded to this component above, if needed (spread should go at the end of this map to preserve behavior)'); } } else { forwardedSxSpread = null; diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index c9220792..c913cc6d 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -29,11 +29,11 @@ void main() { ); const sxPrecedenceFixme = - '// FIXME(mui_system_props_migration) - Previously, it was possible for forwarded system props to overwrite these styles, but not anymore since sx takes precedence over system props.' + '// FIXME(mui_system_props_migration) - Previously, it was possible for forwarded system props to overwrite these migrated styles, but not anymore since sx takes precedence over any system props.' '\n // Double-check that this new behavior is okay.'; const sxMergeFixme = - '// FIXME(mui_system_props_migration) - spread in any sx prop forwarded to this component above, if needed (spread should go after these new styles to preserve behavior)'; + '// FIXME(mui_system_props_migration) - spread in any sx prop forwarded to this component above, if needed (spread should go at the end of this map to preserve behavior)'; test('migrates single system prop to sx', () async { await testSuggestor( From 9e1abc43787ef38b7f9b0340b19186860e58b4fe Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 16:21:24 -0700 Subject: [PATCH 26/43] Clean up, add comments --- .../system_props_to_sx_migrator.dart | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index c5987048..8886bd69 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -97,6 +97,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { } } + // No system props to migrate; bail out for this usage. if (systemProps.isEmpty) return; final migratedSystemPropEntries = []; @@ -130,7 +131,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { prop.node.end); } - final propForwardingSources = _getPropForwardingSources(usage); + final propForwardingSources = _detectPropForwardingSources(usage); final fixmes = []; if (propForwardingSources.isNotEmpty) { @@ -205,7 +206,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final String? forwardedSxSpread; final mightForwardSx = propForwardingSources.any((source) { - final type = source.propsExpression?.staticType?.typeOrBound + final type = source.sourceProps?.staticType?.typeOrBound .tryCast(); if (type != null && type.isPropsClass && @@ -218,7 +219,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (mightForwardSx) { // Try to access the forwarded sx prop to merge it in. bool canSafelyGetForwardedSx; - final props = propForwardingSources.singleOrNull?.propsExpression; + final props = propForwardingSources.singleOrNull?.sourceProps; if (props != null && (props is Identifier || props is PropertyAccess)) { final propsElement = props.staticType?.typeOrBound.tryCast()?.element; @@ -270,18 +271,29 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { mapElements.any((e) => e.contains('\n')); } -enum CommentLocation { ownLine, endOfLine, other } - -class _PropForwardingSource { +/// A source of props being added (spread) to a component usage. +class _PropSpreadSource { + /// The cascaded invocation that adds props. final BuilderMethodInvocation cascadedMethod; - final Expression? propsExpression; - _PropForwardingSource(this.cascadedMethod, this.propsExpression); + /// And expression representing the source of the props being spread, + /// or null if the source is more complex or could not be resolved. + /// + /// Handles common prop forwarding expressions, returning the original + /// expression that the subsetted forwarded props are coming from. + /// + /// For example, this expression would be: + /// + /// - for `..addAll(getSomeProps())` - `getSomeProps()` + /// - for `..addProps(props.getPropsToForward({FooProps}))` - `props` + /// - for `..modifyProps(somePropsModifier)` - props + final Expression? sourceProps; + + _PropSpreadSource(this.cascadedMethod, this.sourceProps); } -/// Returns the set of expressions that are sources of props forwarded to the component in [usage], -/// or `null` for sources that were detected but don't cleanly map to a props expression. -List<_PropForwardingSource> _getPropForwardingSources( +/// Returns the sources of props being added or spread to [usage]. +List<_PropSpreadSource> _detectPropForwardingSources( FluentComponentUsage usage) { return usage.cascadedMethodInvocations .map((c) { @@ -290,23 +302,23 @@ List<_PropForwardingSource> _getPropForwardingSources( switch (methodName) { case 'addUnconsumedProps': - return _PropForwardingSource(c, arg); + return _PropSpreadSource(c, arg); case 'addAll': case 'addProps': if (arg is MethodInvocation && arg.methodName.name == 'getPropsToForward') { - return _PropForwardingSource(c, arg.realTarget); + return _PropSpreadSource(c, arg.realTarget); } - return _PropForwardingSource(c, arg); + return _PropSpreadSource(c, arg); case 'modifyProps': if ((arg is MethodInvocation && arg.methodName.name == 'addPropsToForward')) { - return _PropForwardingSource(c, arg.realTarget); + return _PropSpreadSource(c, arg.realTarget); } if (arg is Identifier && arg.name == 'addUnconsumedProps') { - return _PropForwardingSource(c, null); + return _PropSpreadSource(c, null); } - return _PropForwardingSource(c, null); + return _PropSpreadSource(c, null); default: // Not a method that forwards props. return null; From f38ea61357480a52ae65c0181bccdc5e7ca1be99 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 16:27:10 -0700 Subject: [PATCH 27/43] Update doc comment --- .../system_props_to_sx_migrator.dart | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 8886bd69..22880b89 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -22,13 +22,39 @@ import 'package:over_react_codemod/src/util/component_usage.dart'; import 'package:over_react_codemod/src/util/component_usage_migrator.dart'; import 'package:over_react_codemod/src/util/element_type_helpers.dart'; -/// A suggestor that migrates MUI system props to the `sx` prop. +/// A suggestor that migrates deprecated MUI system props to the `sx` prop, +/// ensuring that existing `sx` prop values are preserved and merged correctly. /// -/// MUI System props (such as `mt={*}`, `bgcolor={*}`, and more) have been deprecated -/// in MUI v6 in favor of the `sx` prop. +/// ### Example migration /// -/// This migrator detects components that use system props and moves those props -/// to the `sx` prop, ensuring that existing `sx` prop values are preserved and merged correctly. +/// Before: +/// ```dart +/// (Box()..m = 2)(); +/// (Box() +/// ..m = 2 +/// ..sx = {'color': '#f00'} +/// )() +/// (Box() +/// ..m = 2 +/// ..addProps(props.getPropsToForward()) +/// )() +/// ``` +/// +/// After +/// ```dart +/// (Box()..sx = {'m': 2})() +/// (Box()..sx = { +/// 'm': 2, +/// 'color': '#f00' +/// })() +/// (Box() +/// ..addProps(props.getPropsToForward()) +/// ..sx = { +/// 'm': 2, +/// ...?props.sx, +/// } +/// )() +/// ``` class SystemPropsToSxMigrator extends ComponentUsageMigrator { @override String get fixmePrefix => 'FIXME(mui_system_props_migration)'; From 392a251005f8c348a69df4cb8887090b9a0f749c Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 16:31:49 -0700 Subject: [PATCH 28/43] Clean up executable --- .../mui_system_props_migration.dart | 36 +++---------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/lib/src/executables/mui_system_props_migration.dart b/lib/src/executables/mui_system_props_migration.dart index b55a3dd0..eacfdee8 100644 --- a/lib/src/executables/mui_system_props_migration.dart +++ b/lib/src/executables/mui_system_props_migration.dart @@ -14,41 +14,14 @@ import 'dart:io'; -import 'package:args/args.dart'; import 'package:codemod/codemod.dart'; import 'package:over_react_codemod/src/ignoreable.dart'; import 'package:over_react_codemod/src/mui_suggestors/system_props_to_sx_migrator.dart'; import 'package:over_react_codemod/src/util.dart'; -const _changesRequiredOutput = """ - To update your code, run the following commands in your repository: - dart pub global activate over_react_codemod - dart pub global run over_react_codemod:mui_system_props_migration -"""; - -/// Migrates MUI system props to the `sx` prop. -/// -/// MUI System props (such as `mt={*}`, `bgcolor={*}`, and more) have been deprecated -/// in MUI v6 in favor of the `sx` prop. -/// -/// This codemod moves all System props to the sx prop, ensuring that existing -/// sx prop values are preserved and merged correctly. void main(List args) async { - final parser = ArgParser.allowAnything(); - - final parsedArgs = parser.parse(args); - - // Work around allowAnything not allowing you to pass flags. - if (parsedArgs.arguments.contains('--help')) { - // Print command description; flags and other output will get printed via runInteractiveCodemod. - print('Migrates MUI system props to the `sx` prop.\n'); - print( - 'MUI System props (such as mt={*}, bgcolor={*}, and more) have been deprecated'); - print('in MUI v6 in favor of the sx prop.\n'); - print( - 'This codemod moves all System props to the sx prop, ensuring that existing'); - print('sx prop values are preserved and merged correctly.\n'); - } + const description = 'Migrates deprecated MUI system props to the `sx` prop,' + '\nensuring that existing `sx` prop values are preserved and merged correctly.'; exitCode = await runInteractiveCodemod( allDartPathsExceptHidden(), @@ -56,8 +29,7 @@ void main(List args) async { SystemPropsToSxMigrator(), ].map((s) => ignoreable(s))), defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, + args: args, + additionalHelpOutput: description, ); } From d9c49a66b9e407efa80b952d4026e62e96974e0d Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 16:50:31 -0700 Subject: [PATCH 29/43] Add tags for tests using private package --- test/mui_suggestors/system_props_to_sx_migrator_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index c913cc6d..31c1922e 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -1128,7 +1128,7 @@ void main() { ); }); }); - }); + }, tags: 'wsd'); } String withHeader(String source) => ''' From eedbe7a9c23b974d56db2ab036546300110766b5 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 17:15:09 -0700 Subject: [PATCH 30/43] Add MUI stub test fixture for sx migrator codemod --- test/resolved_file_context.dart | 5 + test/test_fixtures/mui_stub_project/README.md | 3 + .../mui_stub_project/lib/components.dart | 5 + .../mui_stub_project/lib/components/box.dart | 115 ++++++++++++++++++ .../mui_stub_project/lib/components/grid.dart | 115 ++++++++++++++++++ .../lib/components/stack.dart | 115 ++++++++++++++++++ .../lib/components/text_field.dart | 14 +++ .../lib/components/typography.dart | 115 ++++++++++++++++++ .../mui_stub_project/pubspec.yaml | 5 + 9 files changed, 492 insertions(+) create mode 100644 test/test_fixtures/mui_stub_project/README.md create mode 100644 test/test_fixtures/mui_stub_project/lib/components.dart create mode 100644 test/test_fixtures/mui_stub_project/lib/components/box.dart create mode 100644 test/test_fixtures/mui_stub_project/lib/components/grid.dart create mode 100644 test/test_fixtures/mui_stub_project/lib/components/stack.dart create mode 100644 test/test_fixtures/mui_stub_project/lib/components/text_field.dart create mode 100644 test/test_fixtures/mui_stub_project/lib/components/typography.dart create mode 100644 test/test_fixtures/mui_stub_project/pubspec.yaml diff --git a/test/resolved_file_context.dart b/test/resolved_file_context.dart index 2c2f97d7..31151c65 100644 --- a/test/resolved_file_context.dart +++ b/test/resolved_file_context.dart @@ -70,6 +70,11 @@ class SharedAnalysisContext { static final rmui = SharedAnalysisContext( p.join(findPackageRootFor(p.current), 'test/test_fixtures/rmui_project')); + /// A context root located at `test/test_fixtures/mui_stub_project` + /// that stubs out some MUI components using over_react. + static final muiStub = SharedAnalysisContext( + p.join(findPackageRootFor(p.current), 'test/test_fixtures/mui_stub_project')); + /// The path to the package root in which test files will be created /// and resolved. final String _path; diff --git a/test/test_fixtures/mui_stub_project/README.md b/test/test_fixtures/mui_stub_project/README.md new file mode 100644 index 00000000..572591b7 --- /dev/null +++ b/test/test_fixtures/mui_stub_project/README.md @@ -0,0 +1,3 @@ +A package that stubs out some MUI props/components, and can be used as a context root for tests that require a resolved analysis context with access to them. + +To use, see `SharedAnalysisContext.mui_stub`. diff --git a/test/test_fixtures/mui_stub_project/lib/components.dart b/test/test_fixtures/mui_stub_project/lib/components.dart new file mode 100644 index 00000000..f7710b5e --- /dev/null +++ b/test/test_fixtures/mui_stub_project/lib/components.dart @@ -0,0 +1,5 @@ +export 'components/box.dart'; +export 'components/grid.dart'; +export 'components/stack.dart'; +export 'components/text_field.dart'; +export 'components/typography.dart'; diff --git a/test/test_fixtures/mui_stub_project/lib/components/box.dart b/test/test_fixtures/mui_stub_project/lib/components/box.dart new file mode 100644 index 00000000..6a66d3c1 --- /dev/null +++ b/test/test_fixtures/mui_stub_project/lib/components/box.dart @@ -0,0 +1,115 @@ +import 'package:over_react/over_react.dart'; + +part 'box.over_react.g.dart'; + +UiFactory Box = uiFunction((_) {}), _$BoxConfig); + +@Props(keyNamespace: '') +mixin BoxProps on UiProps { + @convertJsMapProp + Map? sx; + + @Deprecated('Use sx.') dynamic alignContent; + @Deprecated('Use sx.') dynamic alignItems; + @Deprecated('Use sx.') dynamic alignSelf; + @Deprecated('Use sx.') dynamic bgcolor; + @Deprecated('Use sx.') dynamic border; + @Deprecated('Use sx.') dynamic borderBottom; + @Deprecated('Use sx.') dynamic borderColor; + @Deprecated('Use sx.') dynamic borderLeft; + @Deprecated('Use sx.') dynamic borderRadius; + @Deprecated('Use sx.') dynamic borderRight; + @Deprecated('Use sx.') dynamic borderTop; + @Deprecated('Use sx.') dynamic bottom; + @Deprecated('Use sx.') dynamic boxShadow; + @Deprecated('Use sx.') dynamic boxSizing; + @Deprecated('Use sx.') dynamic color; + @Deprecated('Use sx.') dynamic columnGap; + @Deprecated('Use sx.') dynamic display; + @Deprecated('Use sx.') dynamic displayPrint; + @Deprecated('Use sx.') dynamic flex; + @Deprecated('Use sx.') dynamic flexBasis; + @Deprecated('Use sx.') dynamic flexDirection; + @Deprecated('Use sx.') dynamic flexGrow; + @Deprecated('Use sx.') dynamic flexShrink; + @Deprecated('Use sx.') dynamic flexWrap; + @Deprecated('Use sx.') dynamic fontFamily; + @Deprecated('Use sx.') dynamic fontSize; + @Deprecated('Use sx.') dynamic fontStyle; + @Deprecated('Use sx.') dynamic fontWeight; + @Deprecated('Use sx.') dynamic gap; + @Deprecated('Use sx.') dynamic gridArea; + @Deprecated('Use sx.') dynamic gridAutoColumns; + @Deprecated('Use sx.') dynamic gridAutoFlow; + @Deprecated('Use sx.') dynamic gridAutoRows; + @Deprecated('Use sx.') dynamic gridColumn; + @Deprecated('Use sx.') dynamic gridRow; + @Deprecated('Use sx.') dynamic gridTemplateAreas; + @Deprecated('Use sx.') dynamic gridTemplateColumns; + @Deprecated('Use sx.') dynamic gridTemplateRows; + @Deprecated('Use sx.') dynamic height; + @Deprecated('Use sx.') dynamic justifyContent; + @Deprecated('Use sx.') dynamic justifyItems; + @Deprecated('Use sx.') dynamic justifySelf; + @Deprecated('Use sx.') dynamic left; + @Deprecated('Use sx.') dynamic letterSpacing; + @Deprecated('Use sx.') dynamic lineHeight; + @Deprecated('Use sx.') dynamic m; + @Deprecated('Use sx.') dynamic margin; + @Deprecated('Use sx.') dynamic marginBlock; + @Deprecated('Use sx.') dynamic marginBlockEnd; + @Deprecated('Use sx.') dynamic marginBlockStart; + @Deprecated('Use sx.') dynamic marginBottom; + @Deprecated('Use sx.') dynamic marginInline; + @Deprecated('Use sx.') dynamic marginInlineEnd; + @Deprecated('Use sx.') dynamic marginInlineStart; + @Deprecated('Use sx.') dynamic marginLeft; + @Deprecated('Use sx.') dynamic marginRight; + @Deprecated('Use sx.') dynamic marginTop; + @Deprecated('Use sx.') dynamic marginX; + @Deprecated('Use sx.') dynamic marginY; + @Deprecated('Use sx.') dynamic maxHeight; + @Deprecated('Use sx.') dynamic maxWidth; + @Deprecated('Use sx.') dynamic mb; + @Deprecated('Use sx.') dynamic minHeight; + @Deprecated('Use sx.') dynamic minWidth; + @Deprecated('Use sx.') dynamic ml; + @Deprecated('Use sx.') dynamic mr; + @Deprecated('Use sx.') dynamic mt; + @Deprecated('Use sx.') dynamic mx; + @Deprecated('Use sx.') dynamic my; + @Deprecated('Use sx.') dynamic order; + @Deprecated('Use sx.') dynamic overflow; + @Deprecated('Use sx.') dynamic p; + @Deprecated('Use sx.') dynamic padding; + @Deprecated('Use sx.') dynamic paddingBlock; + @Deprecated('Use sx.') dynamic paddingBlockEnd; + @Deprecated('Use sx.') dynamic paddingBlockStart; + @Deprecated('Use sx.') dynamic paddingBottom; + @Deprecated('Use sx.') dynamic paddingInline; + @Deprecated('Use sx.') dynamic paddingInlineEnd; + @Deprecated('Use sx.') dynamic paddingInlineStart; + @Deprecated('Use sx.') dynamic paddingLeft; + @Deprecated('Use sx.') dynamic paddingRight; + @Deprecated('Use sx.') dynamic paddingTop; + @Deprecated('Use sx.') dynamic paddingX; + @Deprecated('Use sx.') dynamic paddingY; + @Deprecated('Use sx.') dynamic pb; + @Deprecated('Use sx.') dynamic pl; + @Deprecated('Use sx.') dynamic position; + @Deprecated('Use sx.') dynamic pr; + @Deprecated('Use sx.') dynamic pt; + @Deprecated('Use sx.') dynamic px; + @Deprecated('Use sx.') dynamic py; + @Deprecated('Use sx.') dynamic right; + @Deprecated('Use sx.') dynamic rowGap; + @Deprecated('Use sx.') dynamic textAlign; + @Deprecated('Use sx.') dynamic textOverflow; + @Deprecated('Use sx.') dynamic textTransform; + @Deprecated('Use sx.') dynamic top; + @Deprecated('Use sx.') dynamic typography; + @Deprecated('Use sx.') dynamic visibility; + @Deprecated('Use sx.') dynamic whiteSpace; + @Deprecated('Use sx.') dynamic width; + @Deprecated('Use sx.') dynamic zIndex; +} diff --git a/test/test_fixtures/mui_stub_project/lib/components/grid.dart b/test/test_fixtures/mui_stub_project/lib/components/grid.dart new file mode 100644 index 00000000..b5d1f983 --- /dev/null +++ b/test/test_fixtures/mui_stub_project/lib/components/grid.dart @@ -0,0 +1,115 @@ +import 'package:over_react/over_react.dart'; + +part 'grid.over_react.g.dart'; + +UiFactory Grid = uiFunction((_) {}), _$GridConfig); + +@Props(keyNamespace: '') +mixin GridProps on UiProps { + @convertJsMapProp + Map? sx; + + @Deprecated('Use sx.') dynamic alignContent; + @Deprecated('Use sx.') dynamic alignItems; + @Deprecated('Use sx.') dynamic alignSelf; + @Deprecated('Use sx.') dynamic bgcolor; + @Deprecated('Use sx.') dynamic border; + @Deprecated('Use sx.') dynamic borderBottom; + @Deprecated('Use sx.') dynamic borderColor; + @Deprecated('Use sx.') dynamic borderLeft; + @Deprecated('Use sx.') dynamic borderRadius; + @Deprecated('Use sx.') dynamic borderRight; + @Deprecated('Use sx.') dynamic borderTop; + @Deprecated('Use sx.') dynamic bottom; + @Deprecated('Use sx.') dynamic boxShadow; + @Deprecated('Use sx.') dynamic boxSizing; + @Deprecated('Use sx.') dynamic color; + @Deprecated('Use sx.') dynamic columnGap; + @Deprecated('Use sx.') dynamic display; + @Deprecated('Use sx.') dynamic displayPrint; + @Deprecated('Use sx.') dynamic flex; + @Deprecated('Use sx.') dynamic flexBasis; + @Deprecated('Use sx.') dynamic flexDirection; + @Deprecated('Use sx.') dynamic flexGrow; + @Deprecated('Use sx.') dynamic flexShrink; + @Deprecated('Use sx.') dynamic flexWrap; + @Deprecated('Use sx.') dynamic fontFamily; + @Deprecated('Use sx.') dynamic fontSize; + @Deprecated('Use sx.') dynamic fontStyle; + @Deprecated('Use sx.') dynamic fontWeight; + @Deprecated('Use sx.') dynamic gap; + @Deprecated('Use sx.') dynamic gridArea; + @Deprecated('Use sx.') dynamic gridAutoColumns; + @Deprecated('Use sx.') dynamic gridAutoFlow; + @Deprecated('Use sx.') dynamic gridAutoRows; + @Deprecated('Use sx.') dynamic gridColumn; + @Deprecated('Use sx.') dynamic gridRow; + @Deprecated('Use sx.') dynamic gridTemplateAreas; + @Deprecated('Use sx.') dynamic gridTemplateColumns; + @Deprecated('Use sx.') dynamic gridTemplateRows; + @Deprecated('Use sx.') dynamic height; + @Deprecated('Use sx.') dynamic justifyContent; + @Deprecated('Use sx.') dynamic justifyItems; + @Deprecated('Use sx.') dynamic justifySelf; + @Deprecated('Use sx.') dynamic left; + @Deprecated('Use sx.') dynamic letterSpacing; + @Deprecated('Use sx.') dynamic lineHeight; + @Deprecated('Use sx.') dynamic m; + @Deprecated('Use sx.') dynamic margin; + @Deprecated('Use sx.') dynamic marginBlock; + @Deprecated('Use sx.') dynamic marginBlockEnd; + @Deprecated('Use sx.') dynamic marginBlockStart; + @Deprecated('Use sx.') dynamic marginBottom; + @Deprecated('Use sx.') dynamic marginInline; + @Deprecated('Use sx.') dynamic marginInlineEnd; + @Deprecated('Use sx.') dynamic marginInlineStart; + @Deprecated('Use sx.') dynamic marginLeft; + @Deprecated('Use sx.') dynamic marginRight; + @Deprecated('Use sx.') dynamic marginTop; + @Deprecated('Use sx.') dynamic marginX; + @Deprecated('Use sx.') dynamic marginY; + @Deprecated('Use sx.') dynamic maxHeight; + @Deprecated('Use sx.') dynamic maxWidth; + @Deprecated('Use sx.') dynamic mb; + @Deprecated('Use sx.') dynamic minHeight; + @Deprecated('Use sx.') dynamic minWidth; + @Deprecated('Use sx.') dynamic ml; + @Deprecated('Use sx.') dynamic mr; + @Deprecated('Use sx.') dynamic mt; + @Deprecated('Use sx.') dynamic mx; + @Deprecated('Use sx.') dynamic my; + @Deprecated('Use sx.') dynamic order; + @Deprecated('Use sx.') dynamic overflow; + @Deprecated('Use sx.') dynamic p; + @Deprecated('Use sx.') dynamic padding; + @Deprecated('Use sx.') dynamic paddingBlock; + @Deprecated('Use sx.') dynamic paddingBlockEnd; + @Deprecated('Use sx.') dynamic paddingBlockStart; + @Deprecated('Use sx.') dynamic paddingBottom; + @Deprecated('Use sx.') dynamic paddingInline; + @Deprecated('Use sx.') dynamic paddingInlineEnd; + @Deprecated('Use sx.') dynamic paddingInlineStart; + @Deprecated('Use sx.') dynamic paddingLeft; + @Deprecated('Use sx.') dynamic paddingRight; + @Deprecated('Use sx.') dynamic paddingTop; + @Deprecated('Use sx.') dynamic paddingX; + @Deprecated('Use sx.') dynamic paddingY; + @Deprecated('Use sx.') dynamic pb; + @Deprecated('Use sx.') dynamic pl; + @Deprecated('Use sx.') dynamic position; + @Deprecated('Use sx.') dynamic pr; + @Deprecated('Use sx.') dynamic pt; + @Deprecated('Use sx.') dynamic px; + @Deprecated('Use sx.') dynamic py; + @Deprecated('Use sx.') dynamic right; + @Deprecated('Use sx.') dynamic rowGap; + @Deprecated('Use sx.') dynamic textAlign; + @Deprecated('Use sx.') dynamic textOverflow; + @Deprecated('Use sx.') dynamic textTransform; + @Deprecated('Use sx.') dynamic top; + @Deprecated('Use sx.') dynamic typography; + @Deprecated('Use sx.') dynamic visibility; + @Deprecated('Use sx.') dynamic whiteSpace; + @Deprecated('Use sx.') dynamic width; + @Deprecated('Use sx.') dynamic zIndex; +} diff --git a/test/test_fixtures/mui_stub_project/lib/components/stack.dart b/test/test_fixtures/mui_stub_project/lib/components/stack.dart new file mode 100644 index 00000000..e3dd63ae --- /dev/null +++ b/test/test_fixtures/mui_stub_project/lib/components/stack.dart @@ -0,0 +1,115 @@ +import 'package:over_react/over_react.dart'; + +part 'stack.over_react.g.dart'; + +UiFactory Stack = uiFunction((_) {}), _$StackConfig); + +@Props(keyNamespace: '') +mixin StackProps on UiProps { + @convertJsMapProp + Map? sx; + + @Deprecated('Use sx.') dynamic alignContent; + @Deprecated('Use sx.') dynamic alignItems; + @Deprecated('Use sx.') dynamic alignSelf; + @Deprecated('Use sx.') dynamic bgcolor; + @Deprecated('Use sx.') dynamic border; + @Deprecated('Use sx.') dynamic borderBottom; + @Deprecated('Use sx.') dynamic borderColor; + @Deprecated('Use sx.') dynamic borderLeft; + @Deprecated('Use sx.') dynamic borderRadius; + @Deprecated('Use sx.') dynamic borderRight; + @Deprecated('Use sx.') dynamic borderTop; + @Deprecated('Use sx.') dynamic bottom; + @Deprecated('Use sx.') dynamic boxShadow; + @Deprecated('Use sx.') dynamic boxSizing; + @Deprecated('Use sx.') dynamic color; + @Deprecated('Use sx.') dynamic columnGap; + @Deprecated('Use sx.') dynamic display; + @Deprecated('Use sx.') dynamic displayPrint; + @Deprecated('Use sx.') dynamic flex; + @Deprecated('Use sx.') dynamic flexBasis; + @Deprecated('Use sx.') dynamic flexDirection; + @Deprecated('Use sx.') dynamic flexGrow; + @Deprecated('Use sx.') dynamic flexShrink; + @Deprecated('Use sx.') dynamic flexWrap; + @Deprecated('Use sx.') dynamic fontFamily; + @Deprecated('Use sx.') dynamic fontSize; + @Deprecated('Use sx.') dynamic fontStyle; + @Deprecated('Use sx.') dynamic fontWeight; + @Deprecated('Use sx.') dynamic gap; + @Deprecated('Use sx.') dynamic gridArea; + @Deprecated('Use sx.') dynamic gridAutoColumns; + @Deprecated('Use sx.') dynamic gridAutoFlow; + @Deprecated('Use sx.') dynamic gridAutoRows; + @Deprecated('Use sx.') dynamic gridColumn; + @Deprecated('Use sx.') dynamic gridRow; + @Deprecated('Use sx.') dynamic gridTemplateAreas; + @Deprecated('Use sx.') dynamic gridTemplateColumns; + @Deprecated('Use sx.') dynamic gridTemplateRows; + @Deprecated('Use sx.') dynamic height; + @Deprecated('Use sx.') dynamic justifyContent; + @Deprecated('Use sx.') dynamic justifyItems; + @Deprecated('Use sx.') dynamic justifySelf; + @Deprecated('Use sx.') dynamic left; + @Deprecated('Use sx.') dynamic letterSpacing; + @Deprecated('Use sx.') dynamic lineHeight; + @Deprecated('Use sx.') dynamic m; + @Deprecated('Use sx.') dynamic margin; + @Deprecated('Use sx.') dynamic marginBlock; + @Deprecated('Use sx.') dynamic marginBlockEnd; + @Deprecated('Use sx.') dynamic marginBlockStart; + @Deprecated('Use sx.') dynamic marginBottom; + @Deprecated('Use sx.') dynamic marginInline; + @Deprecated('Use sx.') dynamic marginInlineEnd; + @Deprecated('Use sx.') dynamic marginInlineStart; + @Deprecated('Use sx.') dynamic marginLeft; + @Deprecated('Use sx.') dynamic marginRight; + @Deprecated('Use sx.') dynamic marginTop; + @Deprecated('Use sx.') dynamic marginX; + @Deprecated('Use sx.') dynamic marginY; + @Deprecated('Use sx.') dynamic maxHeight; + @Deprecated('Use sx.') dynamic maxWidth; + @Deprecated('Use sx.') dynamic mb; + @Deprecated('Use sx.') dynamic minHeight; + @Deprecated('Use sx.') dynamic minWidth; + @Deprecated('Use sx.') dynamic ml; + @Deprecated('Use sx.') dynamic mr; + @Deprecated('Use sx.') dynamic mt; + @Deprecated('Use sx.') dynamic mx; + @Deprecated('Use sx.') dynamic my; + @Deprecated('Use sx.') dynamic order; + @Deprecated('Use sx.') dynamic overflow; + @Deprecated('Use sx.') dynamic p; + @Deprecated('Use sx.') dynamic padding; + @Deprecated('Use sx.') dynamic paddingBlock; + @Deprecated('Use sx.') dynamic paddingBlockEnd; + @Deprecated('Use sx.') dynamic paddingBlockStart; + @Deprecated('Use sx.') dynamic paddingBottom; + @Deprecated('Use sx.') dynamic paddingInline; + @Deprecated('Use sx.') dynamic paddingInlineEnd; + @Deprecated('Use sx.') dynamic paddingInlineStart; + @Deprecated('Use sx.') dynamic paddingLeft; + @Deprecated('Use sx.') dynamic paddingRight; + @Deprecated('Use sx.') dynamic paddingTop; + @Deprecated('Use sx.') dynamic paddingX; + @Deprecated('Use sx.') dynamic paddingY; + @Deprecated('Use sx.') dynamic pb; + @Deprecated('Use sx.') dynamic pl; + @Deprecated('Use sx.') dynamic position; + @Deprecated('Use sx.') dynamic pr; + @Deprecated('Use sx.') dynamic pt; + @Deprecated('Use sx.') dynamic px; + @Deprecated('Use sx.') dynamic py; + @Deprecated('Use sx.') dynamic right; + @Deprecated('Use sx.') dynamic rowGap; + @Deprecated('Use sx.') dynamic textAlign; + @Deprecated('Use sx.') dynamic textOverflow; + @Deprecated('Use sx.') dynamic textTransform; + @Deprecated('Use sx.') dynamic top; + @Deprecated('Use sx.') dynamic typography; + @Deprecated('Use sx.') dynamic visibility; + @Deprecated('Use sx.') dynamic whiteSpace; + @Deprecated('Use sx.') dynamic width; + @Deprecated('Use sx.') dynamic zIndex; +} diff --git a/test/test_fixtures/mui_stub_project/lib/components/text_field.dart b/test/test_fixtures/mui_stub_project/lib/components/text_field.dart new file mode 100644 index 00000000..dfe9f9a1 --- /dev/null +++ b/test/test_fixtures/mui_stub_project/lib/components/text_field.dart @@ -0,0 +1,14 @@ +import 'package:over_react/over_react.dart'; + +part 'text_field.over_react.g.dart'; + +UiFactory TextField = uiFunction((_) {}), _$TextFieldConfig); + +@Props(keyNamespace: '') +mixin TextFieldProps on UiProps { + @convertJsMapProp + Map? sx; + + @Deprecated('Deprecated, but not the same as the system props color') + dynamic color; +} diff --git a/test/test_fixtures/mui_stub_project/lib/components/typography.dart b/test/test_fixtures/mui_stub_project/lib/components/typography.dart new file mode 100644 index 00000000..f43990df --- /dev/null +++ b/test/test_fixtures/mui_stub_project/lib/components/typography.dart @@ -0,0 +1,115 @@ +import 'package:over_react/over_react.dart'; + +part 'typography.over_react.g.dart'; + +UiFactory Typography = uiFunction((_) {}), _$TypographyConfig); + +@Props(keyNamespace: '') +mixin TypographyProps on UiProps { + @convertJsMapProp + Map? sx; + + @Deprecated('Use sx.') dynamic alignContent; + @Deprecated('Use sx.') dynamic alignItems; + @Deprecated('Use sx.') dynamic alignSelf; + @Deprecated('Use sx.') dynamic bgcolor; + @Deprecated('Use sx.') dynamic border; + @Deprecated('Use sx.') dynamic borderBottom; + @Deprecated('Use sx.') dynamic borderColor; + @Deprecated('Use sx.') dynamic borderLeft; + @Deprecated('Use sx.') dynamic borderRadius; + @Deprecated('Use sx.') dynamic borderRight; + @Deprecated('Use sx.') dynamic borderTop; + @Deprecated('Use sx.') dynamic bottom; + @Deprecated('Use sx.') dynamic boxShadow; + @Deprecated('Use sx.') dynamic boxSizing; + @Deprecated('Use sx.') dynamic color; + @Deprecated('Use sx.') dynamic columnGap; + @Deprecated('Use sx.') dynamic display; + @Deprecated('Use sx.') dynamic displayPrint; + @Deprecated('Use sx.') dynamic flex; + @Deprecated('Use sx.') dynamic flexBasis; + @Deprecated('Use sx.') dynamic flexDirection; + @Deprecated('Use sx.') dynamic flexGrow; + @Deprecated('Use sx.') dynamic flexShrink; + @Deprecated('Use sx.') dynamic flexWrap; + @Deprecated('Use sx.') dynamic fontFamily; + @Deprecated('Use sx.') dynamic fontSize; + @Deprecated('Use sx.') dynamic fontStyle; + @Deprecated('Use sx.') dynamic fontWeight; + @Deprecated('Use sx.') dynamic gap; + @Deprecated('Use sx.') dynamic gridArea; + @Deprecated('Use sx.') dynamic gridAutoColumns; + @Deprecated('Use sx.') dynamic gridAutoFlow; + @Deprecated('Use sx.') dynamic gridAutoRows; + @Deprecated('Use sx.') dynamic gridColumn; + @Deprecated('Use sx.') dynamic gridRow; + @Deprecated('Use sx.') dynamic gridTemplateAreas; + @Deprecated('Use sx.') dynamic gridTemplateColumns; + @Deprecated('Use sx.') dynamic gridTemplateRows; + @Deprecated('Use sx.') dynamic height; + @Deprecated('Use sx.') dynamic justifyContent; + @Deprecated('Use sx.') dynamic justifyItems; + @Deprecated('Use sx.') dynamic justifySelf; + @Deprecated('Use sx.') dynamic left; + @Deprecated('Use sx.') dynamic letterSpacing; + @Deprecated('Use sx.') dynamic lineHeight; + @Deprecated('Use sx.') dynamic m; + @Deprecated('Use sx.') dynamic margin; + @Deprecated('Use sx.') dynamic marginBlock; + @Deprecated('Use sx.') dynamic marginBlockEnd; + @Deprecated('Use sx.') dynamic marginBlockStart; + @Deprecated('Use sx.') dynamic marginBottom; + @Deprecated('Use sx.') dynamic marginInline; + @Deprecated('Use sx.') dynamic marginInlineEnd; + @Deprecated('Use sx.') dynamic marginInlineStart; + @Deprecated('Use sx.') dynamic marginLeft; + @Deprecated('Use sx.') dynamic marginRight; + @Deprecated('Use sx.') dynamic marginTop; + @Deprecated('Use sx.') dynamic marginX; + @Deprecated('Use sx.') dynamic marginY; + @Deprecated('Use sx.') dynamic maxHeight; + @Deprecated('Use sx.') dynamic maxWidth; + @Deprecated('Use sx.') dynamic mb; + @Deprecated('Use sx.') dynamic minHeight; + @Deprecated('Use sx.') dynamic minWidth; + @Deprecated('Use sx.') dynamic ml; + @Deprecated('Use sx.') dynamic mr; + @Deprecated('Use sx.') dynamic mt; + @Deprecated('Use sx.') dynamic mx; + @Deprecated('Use sx.') dynamic my; + @Deprecated('Use sx.') dynamic order; + @Deprecated('Use sx.') dynamic overflow; + @Deprecated('Use sx.') dynamic p; + @Deprecated('Use sx.') dynamic padding; + @Deprecated('Use sx.') dynamic paddingBlock; + @Deprecated('Use sx.') dynamic paddingBlockEnd; + @Deprecated('Use sx.') dynamic paddingBlockStart; + @Deprecated('Use sx.') dynamic paddingBottom; + @Deprecated('Use sx.') dynamic paddingInline; + @Deprecated('Use sx.') dynamic paddingInlineEnd; + @Deprecated('Use sx.') dynamic paddingInlineStart; + @Deprecated('Use sx.') dynamic paddingLeft; + @Deprecated('Use sx.') dynamic paddingRight; + @Deprecated('Use sx.') dynamic paddingTop; + @Deprecated('Use sx.') dynamic paddingX; + @Deprecated('Use sx.') dynamic paddingY; + @Deprecated('Use sx.') dynamic pb; + @Deprecated('Use sx.') dynamic pl; + @Deprecated('Use sx.') dynamic position; + @Deprecated('Use sx.') dynamic pr; + @Deprecated('Use sx.') dynamic pt; + @Deprecated('Use sx.') dynamic px; + @Deprecated('Use sx.') dynamic py; + @Deprecated('Use sx.') dynamic right; + @Deprecated('Use sx.') dynamic rowGap; + @Deprecated('Use sx.') dynamic textAlign; + @Deprecated('Use sx.') dynamic textOverflow; + @Deprecated('Use sx.') dynamic textTransform; + @Deprecated('Use sx.') dynamic top; + @Deprecated('Use sx.') dynamic typography; + @Deprecated('Use sx.') dynamic visibility; + @Deprecated('Use sx.') dynamic whiteSpace; + @Deprecated('Use sx.') dynamic width; + @Deprecated('Use sx.') dynamic zIndex; +} diff --git a/test/test_fixtures/mui_stub_project/pubspec.yaml b/test/test_fixtures/mui_stub_project/pubspec.yaml new file mode 100644 index 00000000..5e899d67 --- /dev/null +++ b/test/test_fixtures/mui_stub_project/pubspec.yaml @@ -0,0 +1,5 @@ +name: mui_stub +environment: + sdk: '>=2.19.0 <3.0.0' +dependencies: + over_react: ^5.6.0 From 63bc7ec91e4f6443747f75dcf004038a8ad3c8be Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 17:15:30 -0700 Subject: [PATCH 31/43] Update migrator and test to not rely on unify_ui --- .../system_props_to_sx_migrator.dart | 16 +++++++++------- .../system_props_to_sx_migrator_test.dart | 8 +++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 22880b89..b0a5d8ef 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -112,13 +112,15 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (propName == 'sx') { existingSxProp = prop; } else if (_systemPropNames.contains(propName)) { - final propClassElement = prop.staticElement?.enclosingElement; - final componentName = - propClassElement?.name?.replaceAll(RegExp(r'Props(Mixin)?$'), ''); - if (propClassElement != null && - _componentsWithDeprecatedSystemProps.contains(componentName) && - propClassElement.isDeclaredInPackage('unify_ui')) { - systemProps.add(prop); + final propElement = prop.staticElement?.nonSynthetic; + final declaringPropsElement = propElement?.enclosingElement; + if (propElement != null && declaringPropsElement != null) { + final componentName = declaringPropsElement.name + ?.replaceAll(RegExp(r'Props(Mixin)?$'), ''); + if (_componentsWithDeprecatedSystemProps.contains(componentName) && + propElement.hasDeprecated) { + systemProps.add(prop); + } } } } diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 31c1922e..5b7c315d 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -20,7 +20,7 @@ import '../util.dart'; void main() { group('SystemPropsToSxMigrator', () { - final resolvedContext = SharedAnalysisContext.wsd; + final resolvedContext = SharedAnalysisContext.muiStub; setUpAll(resolvedContext.warmUpAnalysis); final testSuggestor = getSuggestorTester( @@ -1128,14 +1128,12 @@ void main() { ); }); }); - }, tags: 'wsd'); + }); } String withHeader(String source) => ''' - //@dart=2.19 - import 'package:over_react/over_react.dart'; - import 'package:unify_ui/unify_ui.dart'; + import 'package:mui_stub/components.dart'; $source '''; From 3c14ba3cfa689452a33bbfd72612ca7cf15834b4 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 17:16:22 -0700 Subject: [PATCH 32/43] Format --- test/resolved_file_context.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/resolved_file_context.dart b/test/resolved_file_context.dart index 31151c65..570ab029 100644 --- a/test/resolved_file_context.dart +++ b/test/resolved_file_context.dart @@ -72,8 +72,8 @@ class SharedAnalysisContext { /// A context root located at `test/test_fixtures/mui_stub_project` /// that stubs out some MUI components using over_react. - static final muiStub = SharedAnalysisContext( - p.join(findPackageRootFor(p.current), 'test/test_fixtures/mui_stub_project')); + static final muiStub = SharedAnalysisContext(p.join( + findPackageRootFor(p.current), 'test/test_fixtures/mui_stub_project')); /// The path to the package root in which test files will be created /// and resolved. From e7072d1d577a2c59612be70a5f41a8c544df1d14 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 17:16:31 -0700 Subject: [PATCH 33/43] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1126b7ad..d3f78846 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Unreleased +- Add mui_system_props_migration codemod to migrate from system props to sx + ## 2.37.1 - Use gha-dart-oss From b67d7d3bd3d871f0b256f3439f02073d3182288b Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 17:16:58 -0700 Subject: [PATCH 34/43] Revert wsd_project changes --- test/test_fixtures/wsd_project/pubspec.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/test_fixtures/wsd_project/pubspec.yaml b/test/test_fixtures/wsd_project/pubspec.yaml index c7132afe..3befe900 100644 --- a/test/test_fixtures/wsd_project/pubspec.yaml +++ b/test/test_fixtures/wsd_project/pubspec.yaml @@ -8,9 +8,3 @@ dependencies: name: web_skin_dart url: https://pub.workiva.org version: ^3.0.0 - unify_ui: - hosted: - name: unify_ui - url: https://pub.workiva.org - # First version system props were deprecated - version: ^2.20.5 From 65d1d4d5e925c7679e5fe5b95455c69ff8736ff8 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 18:50:10 -0700 Subject: [PATCH 35/43] Set up MUI stubs in test file --- .../system_props_to_sx_migrator.dart | 4 +- .../system_props_to_sx_migrator_test.dart | 86 ++++++++++++++++--- .../over_react_project/pubspec.yaml | 2 +- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index b0a5d8ef..62dbc73e 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -111,7 +111,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final propName = prop.name.name; if (propName == 'sx') { existingSxProp = prop; - } else if (_systemPropNames.contains(propName)) { + } else if (systemPropNames.contains(propName)) { final propElement = prop.staticElement?.nonSynthetic; final declaringPropsElement = propElement?.enclosingElement; if (propElement != null && declaringPropsElement != null) { @@ -363,7 +363,7 @@ const _componentsWithDeprecatedSystemProps = { 'Typography', }; -const _systemPropNames = { +const systemPropNames = { 'm', 'mt', 'mr', diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 5b7c315d..47dc0f72 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:convert'; + import 'package:over_react_codemod/src/mui_suggestors/system_props_to_sx_migrator.dart'; import 'package:test/test.dart'; @@ -20,13 +22,28 @@ import '../util.dart'; void main() { group('SystemPropsToSxMigrator', () { - final resolvedContext = SharedAnalysisContext.muiStub; - setUpAll(resolvedContext.warmUpAnalysis); + final resolvedContext = SharedAnalysisContext.overReact; - final testSuggestor = getSuggestorTester( - SystemPropsToSxMigrator(), - resolvedContext: resolvedContext, - ); + late String muiUri; + + setUpAll(() async { + await resolvedContext.warmUpAnalysis(); + + // Set up a file with stubbed MUI components with system props, for the + // test inputs to import and the suggestor to detect. + final muiComponentsFile = await resolvedContext.resolvedFileContextForTest( + getStubMuiComponentSource(filenameWithoutExtension: 'mui_components'), + filename: 'mui_components.dart', + ); + muiUri = Uri.file(muiComponentsFile.path).toString(); + }); + + String withHeader(String source) => ''' + //@dart=2.19 + import 'package:over_react/over_react.dart'; + import ${jsonEncode(muiUri.toString())}; + $source + '''; const sxPrecedenceFixme = '// FIXME(mui_system_props_migration) - Previously, it was possible for forwarded system props to overwrite these migrated styles, but not anymore since sx takes precedence over any system props.' @@ -35,6 +52,11 @@ void main() { const sxMergeFixme = '// FIXME(mui_system_props_migration) - spread in any sx prop forwarded to this component above, if needed (spread should go at the end of this map to preserve behavior)'; + final testSuggestor = getSuggestorTester( + SystemPropsToSxMigrator(), + resolvedContext: resolvedContext, + ); + test('migrates single system prop to sx', () async { await testSuggestor( input: withHeader(''' @@ -1131,9 +1153,49 @@ void main() { }); } -String withHeader(String source) => ''' - import 'package:over_react/over_react.dart'; - import 'package:mui_stub/components.dart'; - - $source -'''; +String getStubMuiComponentSource({required String filenameWithoutExtension}) { + const componentsWithSystemProps = [ + 'Box', + 'Stack', + 'Grid', + 'Typography', + ]; + return ''' + //@dart=2.19 + import 'package:over_react/over_react.dart'; + import 'package:over_react/js_component.dart'; + + // ignore: uri_has_not_been_generated + part '$filenameWithoutExtension.over_react.g.dart'; + + ${componentsWithSystemProps.map(systemPropsComponentSource).join('\n\n')} + + UiFactory TextField = uiFunction((_) {}, _\$TextFieldConfig); + + @Props(keyNamespace: '') + mixin TextFieldProps on UiProps { + @convertJsMapProp + Map? sx; + + @Deprecated('Deprecated, but not the same as the system props color') + dynamic color; + } + '''; +} + +String systemPropsComponentSource(String componentName) { + final systemProps = systemPropNames + .map((propName) => " @Deprecated('Use sx.') dynamic ${propName};") + .join('\n'); + return ''' + UiFactory<${componentName}Props> $componentName = uiFunction((_) {}, _\$${componentName}Config); + + @Props(keyNamespace: '') + mixin ${componentName}Props on UiProps { + @convertJsMapProp + Map? sx; + + ${systemProps} + } + '''; +} diff --git a/test/test_fixtures/over_react_project/pubspec.yaml b/test/test_fixtures/over_react_project/pubspec.yaml index 5949bbd1..36042ef2 100644 --- a/test/test_fixtures/over_react_project/pubspec.yaml +++ b/test/test_fixtures/over_react_project/pubspec.yaml @@ -2,4 +2,4 @@ name: over_react_project environment: sdk: '>=2.11.0 <3.0.0' dependencies: - over_react: ^5.0.0 + over_react: ^5.6.0 From cee78d4a267f0bab49edb628288cfb47adbd6eca Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 18:54:31 -0700 Subject: [PATCH 36/43] Set up MUI stubs in test file --- .../system_props_to_sx_migrator_test.dart | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 47dc0f72..900817a2 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -31,11 +31,11 @@ void main() { // Set up a file with stubbed MUI components with system props, for the // test inputs to import and the suggestor to detect. - final muiComponentsFile = await resolvedContext.resolvedFileContextForTest( - getStubMuiComponentSource(filenameWithoutExtension: 'mui_components'), + final muiFile = await resolvedContext.resolvedFileContextForTest( + getStubMuiLibrarySource(filenameWithoutExtension: 'mui_components'), filename: 'mui_components.dart', ); - muiUri = Uri.file(muiComponentsFile.path).toString(); + muiUri = Uri.file(muiFile.path).toString(); }); String withHeader(String source) => ''' @@ -1153,13 +1153,26 @@ void main() { }); } -String getStubMuiComponentSource({required String filenameWithoutExtension}) { - const componentsWithSystemProps = [ +String getStubMuiLibrarySource({required String filenameWithoutExtension}) { + final systemPropComponentsSource = [ 'Box', 'Stack', 'Grid', 'Typography', - ]; + ].map((componentName) { + return ''' + UiFactory<${componentName}Props> $componentName = uiFunction((_) {}, _\$${componentName}Config); + + @Props(keyNamespace: '') + mixin ${componentName}Props on UiProps { + @convertJsMapProp + Map? sx; + + ${systemPropNames.map((propName) => " @Deprecated('Use sx.') dynamic ${propName};").join('\n')} + } + '''; + }).join('\n\n'); + return ''' //@dart=2.19 import 'package:over_react/over_react.dart'; @@ -1168,7 +1181,7 @@ String getStubMuiComponentSource({required String filenameWithoutExtension}) { // ignore: uri_has_not_been_generated part '$filenameWithoutExtension.over_react.g.dart'; - ${componentsWithSystemProps.map(systemPropsComponentSource).join('\n\n')} + $systemPropComponentsSource UiFactory TextField = uiFunction((_) {}, _\$TextFieldConfig); @@ -1182,20 +1195,3 @@ String getStubMuiComponentSource({required String filenameWithoutExtension}) { } '''; } - -String systemPropsComponentSource(String componentName) { - final systemProps = systemPropNames - .map((propName) => " @Deprecated('Use sx.') dynamic ${propName};") - .join('\n'); - return ''' - UiFactory<${componentName}Props> $componentName = uiFunction((_) {}, _\$${componentName}Config); - - @Props(keyNamespace: '') - mixin ${componentName}Props on UiProps { - @convertJsMapProp - Map? sx; - - ${systemProps} - } - '''; -} From 2f81b79bb96f304c3aee3b0d9bbbdc3a39b1f377 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 18:55:10 -0700 Subject: [PATCH 37/43] Revert "Add MUI stub test fixture for sx migrator codemod" This reverts commit eedbe7a9 --- test/resolved_file_context.dart | 5 - test/test_fixtures/mui_stub_project/README.md | 3 - .../mui_stub_project/lib/components.dart | 5 - .../mui_stub_project/lib/components/box.dart | 115 ------------------ .../mui_stub_project/lib/components/grid.dart | 115 ------------------ .../lib/components/stack.dart | 115 ------------------ .../lib/components/text_field.dart | 14 --- .../lib/components/typography.dart | 115 ------------------ .../mui_stub_project/pubspec.yaml | 5 - 9 files changed, 492 deletions(-) delete mode 100644 test/test_fixtures/mui_stub_project/README.md delete mode 100644 test/test_fixtures/mui_stub_project/lib/components.dart delete mode 100644 test/test_fixtures/mui_stub_project/lib/components/box.dart delete mode 100644 test/test_fixtures/mui_stub_project/lib/components/grid.dart delete mode 100644 test/test_fixtures/mui_stub_project/lib/components/stack.dart delete mode 100644 test/test_fixtures/mui_stub_project/lib/components/text_field.dart delete mode 100644 test/test_fixtures/mui_stub_project/lib/components/typography.dart delete mode 100644 test/test_fixtures/mui_stub_project/pubspec.yaml diff --git a/test/resolved_file_context.dart b/test/resolved_file_context.dart index 570ab029..2c2f97d7 100644 --- a/test/resolved_file_context.dart +++ b/test/resolved_file_context.dart @@ -70,11 +70,6 @@ class SharedAnalysisContext { static final rmui = SharedAnalysisContext( p.join(findPackageRootFor(p.current), 'test/test_fixtures/rmui_project')); - /// A context root located at `test/test_fixtures/mui_stub_project` - /// that stubs out some MUI components using over_react. - static final muiStub = SharedAnalysisContext(p.join( - findPackageRootFor(p.current), 'test/test_fixtures/mui_stub_project')); - /// The path to the package root in which test files will be created /// and resolved. final String _path; diff --git a/test/test_fixtures/mui_stub_project/README.md b/test/test_fixtures/mui_stub_project/README.md deleted file mode 100644 index 572591b7..00000000 --- a/test/test_fixtures/mui_stub_project/README.md +++ /dev/null @@ -1,3 +0,0 @@ -A package that stubs out some MUI props/components, and can be used as a context root for tests that require a resolved analysis context with access to them. - -To use, see `SharedAnalysisContext.mui_stub`. diff --git a/test/test_fixtures/mui_stub_project/lib/components.dart b/test/test_fixtures/mui_stub_project/lib/components.dart deleted file mode 100644 index f7710b5e..00000000 --- a/test/test_fixtures/mui_stub_project/lib/components.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'components/box.dart'; -export 'components/grid.dart'; -export 'components/stack.dart'; -export 'components/text_field.dart'; -export 'components/typography.dart'; diff --git a/test/test_fixtures/mui_stub_project/lib/components/box.dart b/test/test_fixtures/mui_stub_project/lib/components/box.dart deleted file mode 100644 index 6a66d3c1..00000000 --- a/test/test_fixtures/mui_stub_project/lib/components/box.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:over_react/over_react.dart'; - -part 'box.over_react.g.dart'; - -UiFactory Box = uiFunction((_) {}), _$BoxConfig); - -@Props(keyNamespace: '') -mixin BoxProps on UiProps { - @convertJsMapProp - Map? sx; - - @Deprecated('Use sx.') dynamic alignContent; - @Deprecated('Use sx.') dynamic alignItems; - @Deprecated('Use sx.') dynamic alignSelf; - @Deprecated('Use sx.') dynamic bgcolor; - @Deprecated('Use sx.') dynamic border; - @Deprecated('Use sx.') dynamic borderBottom; - @Deprecated('Use sx.') dynamic borderColor; - @Deprecated('Use sx.') dynamic borderLeft; - @Deprecated('Use sx.') dynamic borderRadius; - @Deprecated('Use sx.') dynamic borderRight; - @Deprecated('Use sx.') dynamic borderTop; - @Deprecated('Use sx.') dynamic bottom; - @Deprecated('Use sx.') dynamic boxShadow; - @Deprecated('Use sx.') dynamic boxSizing; - @Deprecated('Use sx.') dynamic color; - @Deprecated('Use sx.') dynamic columnGap; - @Deprecated('Use sx.') dynamic display; - @Deprecated('Use sx.') dynamic displayPrint; - @Deprecated('Use sx.') dynamic flex; - @Deprecated('Use sx.') dynamic flexBasis; - @Deprecated('Use sx.') dynamic flexDirection; - @Deprecated('Use sx.') dynamic flexGrow; - @Deprecated('Use sx.') dynamic flexShrink; - @Deprecated('Use sx.') dynamic flexWrap; - @Deprecated('Use sx.') dynamic fontFamily; - @Deprecated('Use sx.') dynamic fontSize; - @Deprecated('Use sx.') dynamic fontStyle; - @Deprecated('Use sx.') dynamic fontWeight; - @Deprecated('Use sx.') dynamic gap; - @Deprecated('Use sx.') dynamic gridArea; - @Deprecated('Use sx.') dynamic gridAutoColumns; - @Deprecated('Use sx.') dynamic gridAutoFlow; - @Deprecated('Use sx.') dynamic gridAutoRows; - @Deprecated('Use sx.') dynamic gridColumn; - @Deprecated('Use sx.') dynamic gridRow; - @Deprecated('Use sx.') dynamic gridTemplateAreas; - @Deprecated('Use sx.') dynamic gridTemplateColumns; - @Deprecated('Use sx.') dynamic gridTemplateRows; - @Deprecated('Use sx.') dynamic height; - @Deprecated('Use sx.') dynamic justifyContent; - @Deprecated('Use sx.') dynamic justifyItems; - @Deprecated('Use sx.') dynamic justifySelf; - @Deprecated('Use sx.') dynamic left; - @Deprecated('Use sx.') dynamic letterSpacing; - @Deprecated('Use sx.') dynamic lineHeight; - @Deprecated('Use sx.') dynamic m; - @Deprecated('Use sx.') dynamic margin; - @Deprecated('Use sx.') dynamic marginBlock; - @Deprecated('Use sx.') dynamic marginBlockEnd; - @Deprecated('Use sx.') dynamic marginBlockStart; - @Deprecated('Use sx.') dynamic marginBottom; - @Deprecated('Use sx.') dynamic marginInline; - @Deprecated('Use sx.') dynamic marginInlineEnd; - @Deprecated('Use sx.') dynamic marginInlineStart; - @Deprecated('Use sx.') dynamic marginLeft; - @Deprecated('Use sx.') dynamic marginRight; - @Deprecated('Use sx.') dynamic marginTop; - @Deprecated('Use sx.') dynamic marginX; - @Deprecated('Use sx.') dynamic marginY; - @Deprecated('Use sx.') dynamic maxHeight; - @Deprecated('Use sx.') dynamic maxWidth; - @Deprecated('Use sx.') dynamic mb; - @Deprecated('Use sx.') dynamic minHeight; - @Deprecated('Use sx.') dynamic minWidth; - @Deprecated('Use sx.') dynamic ml; - @Deprecated('Use sx.') dynamic mr; - @Deprecated('Use sx.') dynamic mt; - @Deprecated('Use sx.') dynamic mx; - @Deprecated('Use sx.') dynamic my; - @Deprecated('Use sx.') dynamic order; - @Deprecated('Use sx.') dynamic overflow; - @Deprecated('Use sx.') dynamic p; - @Deprecated('Use sx.') dynamic padding; - @Deprecated('Use sx.') dynamic paddingBlock; - @Deprecated('Use sx.') dynamic paddingBlockEnd; - @Deprecated('Use sx.') dynamic paddingBlockStart; - @Deprecated('Use sx.') dynamic paddingBottom; - @Deprecated('Use sx.') dynamic paddingInline; - @Deprecated('Use sx.') dynamic paddingInlineEnd; - @Deprecated('Use sx.') dynamic paddingInlineStart; - @Deprecated('Use sx.') dynamic paddingLeft; - @Deprecated('Use sx.') dynamic paddingRight; - @Deprecated('Use sx.') dynamic paddingTop; - @Deprecated('Use sx.') dynamic paddingX; - @Deprecated('Use sx.') dynamic paddingY; - @Deprecated('Use sx.') dynamic pb; - @Deprecated('Use sx.') dynamic pl; - @Deprecated('Use sx.') dynamic position; - @Deprecated('Use sx.') dynamic pr; - @Deprecated('Use sx.') dynamic pt; - @Deprecated('Use sx.') dynamic px; - @Deprecated('Use sx.') dynamic py; - @Deprecated('Use sx.') dynamic right; - @Deprecated('Use sx.') dynamic rowGap; - @Deprecated('Use sx.') dynamic textAlign; - @Deprecated('Use sx.') dynamic textOverflow; - @Deprecated('Use sx.') dynamic textTransform; - @Deprecated('Use sx.') dynamic top; - @Deprecated('Use sx.') dynamic typography; - @Deprecated('Use sx.') dynamic visibility; - @Deprecated('Use sx.') dynamic whiteSpace; - @Deprecated('Use sx.') dynamic width; - @Deprecated('Use sx.') dynamic zIndex; -} diff --git a/test/test_fixtures/mui_stub_project/lib/components/grid.dart b/test/test_fixtures/mui_stub_project/lib/components/grid.dart deleted file mode 100644 index b5d1f983..00000000 --- a/test/test_fixtures/mui_stub_project/lib/components/grid.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:over_react/over_react.dart'; - -part 'grid.over_react.g.dart'; - -UiFactory Grid = uiFunction((_) {}), _$GridConfig); - -@Props(keyNamespace: '') -mixin GridProps on UiProps { - @convertJsMapProp - Map? sx; - - @Deprecated('Use sx.') dynamic alignContent; - @Deprecated('Use sx.') dynamic alignItems; - @Deprecated('Use sx.') dynamic alignSelf; - @Deprecated('Use sx.') dynamic bgcolor; - @Deprecated('Use sx.') dynamic border; - @Deprecated('Use sx.') dynamic borderBottom; - @Deprecated('Use sx.') dynamic borderColor; - @Deprecated('Use sx.') dynamic borderLeft; - @Deprecated('Use sx.') dynamic borderRadius; - @Deprecated('Use sx.') dynamic borderRight; - @Deprecated('Use sx.') dynamic borderTop; - @Deprecated('Use sx.') dynamic bottom; - @Deprecated('Use sx.') dynamic boxShadow; - @Deprecated('Use sx.') dynamic boxSizing; - @Deprecated('Use sx.') dynamic color; - @Deprecated('Use sx.') dynamic columnGap; - @Deprecated('Use sx.') dynamic display; - @Deprecated('Use sx.') dynamic displayPrint; - @Deprecated('Use sx.') dynamic flex; - @Deprecated('Use sx.') dynamic flexBasis; - @Deprecated('Use sx.') dynamic flexDirection; - @Deprecated('Use sx.') dynamic flexGrow; - @Deprecated('Use sx.') dynamic flexShrink; - @Deprecated('Use sx.') dynamic flexWrap; - @Deprecated('Use sx.') dynamic fontFamily; - @Deprecated('Use sx.') dynamic fontSize; - @Deprecated('Use sx.') dynamic fontStyle; - @Deprecated('Use sx.') dynamic fontWeight; - @Deprecated('Use sx.') dynamic gap; - @Deprecated('Use sx.') dynamic gridArea; - @Deprecated('Use sx.') dynamic gridAutoColumns; - @Deprecated('Use sx.') dynamic gridAutoFlow; - @Deprecated('Use sx.') dynamic gridAutoRows; - @Deprecated('Use sx.') dynamic gridColumn; - @Deprecated('Use sx.') dynamic gridRow; - @Deprecated('Use sx.') dynamic gridTemplateAreas; - @Deprecated('Use sx.') dynamic gridTemplateColumns; - @Deprecated('Use sx.') dynamic gridTemplateRows; - @Deprecated('Use sx.') dynamic height; - @Deprecated('Use sx.') dynamic justifyContent; - @Deprecated('Use sx.') dynamic justifyItems; - @Deprecated('Use sx.') dynamic justifySelf; - @Deprecated('Use sx.') dynamic left; - @Deprecated('Use sx.') dynamic letterSpacing; - @Deprecated('Use sx.') dynamic lineHeight; - @Deprecated('Use sx.') dynamic m; - @Deprecated('Use sx.') dynamic margin; - @Deprecated('Use sx.') dynamic marginBlock; - @Deprecated('Use sx.') dynamic marginBlockEnd; - @Deprecated('Use sx.') dynamic marginBlockStart; - @Deprecated('Use sx.') dynamic marginBottom; - @Deprecated('Use sx.') dynamic marginInline; - @Deprecated('Use sx.') dynamic marginInlineEnd; - @Deprecated('Use sx.') dynamic marginInlineStart; - @Deprecated('Use sx.') dynamic marginLeft; - @Deprecated('Use sx.') dynamic marginRight; - @Deprecated('Use sx.') dynamic marginTop; - @Deprecated('Use sx.') dynamic marginX; - @Deprecated('Use sx.') dynamic marginY; - @Deprecated('Use sx.') dynamic maxHeight; - @Deprecated('Use sx.') dynamic maxWidth; - @Deprecated('Use sx.') dynamic mb; - @Deprecated('Use sx.') dynamic minHeight; - @Deprecated('Use sx.') dynamic minWidth; - @Deprecated('Use sx.') dynamic ml; - @Deprecated('Use sx.') dynamic mr; - @Deprecated('Use sx.') dynamic mt; - @Deprecated('Use sx.') dynamic mx; - @Deprecated('Use sx.') dynamic my; - @Deprecated('Use sx.') dynamic order; - @Deprecated('Use sx.') dynamic overflow; - @Deprecated('Use sx.') dynamic p; - @Deprecated('Use sx.') dynamic padding; - @Deprecated('Use sx.') dynamic paddingBlock; - @Deprecated('Use sx.') dynamic paddingBlockEnd; - @Deprecated('Use sx.') dynamic paddingBlockStart; - @Deprecated('Use sx.') dynamic paddingBottom; - @Deprecated('Use sx.') dynamic paddingInline; - @Deprecated('Use sx.') dynamic paddingInlineEnd; - @Deprecated('Use sx.') dynamic paddingInlineStart; - @Deprecated('Use sx.') dynamic paddingLeft; - @Deprecated('Use sx.') dynamic paddingRight; - @Deprecated('Use sx.') dynamic paddingTop; - @Deprecated('Use sx.') dynamic paddingX; - @Deprecated('Use sx.') dynamic paddingY; - @Deprecated('Use sx.') dynamic pb; - @Deprecated('Use sx.') dynamic pl; - @Deprecated('Use sx.') dynamic position; - @Deprecated('Use sx.') dynamic pr; - @Deprecated('Use sx.') dynamic pt; - @Deprecated('Use sx.') dynamic px; - @Deprecated('Use sx.') dynamic py; - @Deprecated('Use sx.') dynamic right; - @Deprecated('Use sx.') dynamic rowGap; - @Deprecated('Use sx.') dynamic textAlign; - @Deprecated('Use sx.') dynamic textOverflow; - @Deprecated('Use sx.') dynamic textTransform; - @Deprecated('Use sx.') dynamic top; - @Deprecated('Use sx.') dynamic typography; - @Deprecated('Use sx.') dynamic visibility; - @Deprecated('Use sx.') dynamic whiteSpace; - @Deprecated('Use sx.') dynamic width; - @Deprecated('Use sx.') dynamic zIndex; -} diff --git a/test/test_fixtures/mui_stub_project/lib/components/stack.dart b/test/test_fixtures/mui_stub_project/lib/components/stack.dart deleted file mode 100644 index e3dd63ae..00000000 --- a/test/test_fixtures/mui_stub_project/lib/components/stack.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:over_react/over_react.dart'; - -part 'stack.over_react.g.dart'; - -UiFactory Stack = uiFunction((_) {}), _$StackConfig); - -@Props(keyNamespace: '') -mixin StackProps on UiProps { - @convertJsMapProp - Map? sx; - - @Deprecated('Use sx.') dynamic alignContent; - @Deprecated('Use sx.') dynamic alignItems; - @Deprecated('Use sx.') dynamic alignSelf; - @Deprecated('Use sx.') dynamic bgcolor; - @Deprecated('Use sx.') dynamic border; - @Deprecated('Use sx.') dynamic borderBottom; - @Deprecated('Use sx.') dynamic borderColor; - @Deprecated('Use sx.') dynamic borderLeft; - @Deprecated('Use sx.') dynamic borderRadius; - @Deprecated('Use sx.') dynamic borderRight; - @Deprecated('Use sx.') dynamic borderTop; - @Deprecated('Use sx.') dynamic bottom; - @Deprecated('Use sx.') dynamic boxShadow; - @Deprecated('Use sx.') dynamic boxSizing; - @Deprecated('Use sx.') dynamic color; - @Deprecated('Use sx.') dynamic columnGap; - @Deprecated('Use sx.') dynamic display; - @Deprecated('Use sx.') dynamic displayPrint; - @Deprecated('Use sx.') dynamic flex; - @Deprecated('Use sx.') dynamic flexBasis; - @Deprecated('Use sx.') dynamic flexDirection; - @Deprecated('Use sx.') dynamic flexGrow; - @Deprecated('Use sx.') dynamic flexShrink; - @Deprecated('Use sx.') dynamic flexWrap; - @Deprecated('Use sx.') dynamic fontFamily; - @Deprecated('Use sx.') dynamic fontSize; - @Deprecated('Use sx.') dynamic fontStyle; - @Deprecated('Use sx.') dynamic fontWeight; - @Deprecated('Use sx.') dynamic gap; - @Deprecated('Use sx.') dynamic gridArea; - @Deprecated('Use sx.') dynamic gridAutoColumns; - @Deprecated('Use sx.') dynamic gridAutoFlow; - @Deprecated('Use sx.') dynamic gridAutoRows; - @Deprecated('Use sx.') dynamic gridColumn; - @Deprecated('Use sx.') dynamic gridRow; - @Deprecated('Use sx.') dynamic gridTemplateAreas; - @Deprecated('Use sx.') dynamic gridTemplateColumns; - @Deprecated('Use sx.') dynamic gridTemplateRows; - @Deprecated('Use sx.') dynamic height; - @Deprecated('Use sx.') dynamic justifyContent; - @Deprecated('Use sx.') dynamic justifyItems; - @Deprecated('Use sx.') dynamic justifySelf; - @Deprecated('Use sx.') dynamic left; - @Deprecated('Use sx.') dynamic letterSpacing; - @Deprecated('Use sx.') dynamic lineHeight; - @Deprecated('Use sx.') dynamic m; - @Deprecated('Use sx.') dynamic margin; - @Deprecated('Use sx.') dynamic marginBlock; - @Deprecated('Use sx.') dynamic marginBlockEnd; - @Deprecated('Use sx.') dynamic marginBlockStart; - @Deprecated('Use sx.') dynamic marginBottom; - @Deprecated('Use sx.') dynamic marginInline; - @Deprecated('Use sx.') dynamic marginInlineEnd; - @Deprecated('Use sx.') dynamic marginInlineStart; - @Deprecated('Use sx.') dynamic marginLeft; - @Deprecated('Use sx.') dynamic marginRight; - @Deprecated('Use sx.') dynamic marginTop; - @Deprecated('Use sx.') dynamic marginX; - @Deprecated('Use sx.') dynamic marginY; - @Deprecated('Use sx.') dynamic maxHeight; - @Deprecated('Use sx.') dynamic maxWidth; - @Deprecated('Use sx.') dynamic mb; - @Deprecated('Use sx.') dynamic minHeight; - @Deprecated('Use sx.') dynamic minWidth; - @Deprecated('Use sx.') dynamic ml; - @Deprecated('Use sx.') dynamic mr; - @Deprecated('Use sx.') dynamic mt; - @Deprecated('Use sx.') dynamic mx; - @Deprecated('Use sx.') dynamic my; - @Deprecated('Use sx.') dynamic order; - @Deprecated('Use sx.') dynamic overflow; - @Deprecated('Use sx.') dynamic p; - @Deprecated('Use sx.') dynamic padding; - @Deprecated('Use sx.') dynamic paddingBlock; - @Deprecated('Use sx.') dynamic paddingBlockEnd; - @Deprecated('Use sx.') dynamic paddingBlockStart; - @Deprecated('Use sx.') dynamic paddingBottom; - @Deprecated('Use sx.') dynamic paddingInline; - @Deprecated('Use sx.') dynamic paddingInlineEnd; - @Deprecated('Use sx.') dynamic paddingInlineStart; - @Deprecated('Use sx.') dynamic paddingLeft; - @Deprecated('Use sx.') dynamic paddingRight; - @Deprecated('Use sx.') dynamic paddingTop; - @Deprecated('Use sx.') dynamic paddingX; - @Deprecated('Use sx.') dynamic paddingY; - @Deprecated('Use sx.') dynamic pb; - @Deprecated('Use sx.') dynamic pl; - @Deprecated('Use sx.') dynamic position; - @Deprecated('Use sx.') dynamic pr; - @Deprecated('Use sx.') dynamic pt; - @Deprecated('Use sx.') dynamic px; - @Deprecated('Use sx.') dynamic py; - @Deprecated('Use sx.') dynamic right; - @Deprecated('Use sx.') dynamic rowGap; - @Deprecated('Use sx.') dynamic textAlign; - @Deprecated('Use sx.') dynamic textOverflow; - @Deprecated('Use sx.') dynamic textTransform; - @Deprecated('Use sx.') dynamic top; - @Deprecated('Use sx.') dynamic typography; - @Deprecated('Use sx.') dynamic visibility; - @Deprecated('Use sx.') dynamic whiteSpace; - @Deprecated('Use sx.') dynamic width; - @Deprecated('Use sx.') dynamic zIndex; -} diff --git a/test/test_fixtures/mui_stub_project/lib/components/text_field.dart b/test/test_fixtures/mui_stub_project/lib/components/text_field.dart deleted file mode 100644 index dfe9f9a1..00000000 --- a/test/test_fixtures/mui_stub_project/lib/components/text_field.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:over_react/over_react.dart'; - -part 'text_field.over_react.g.dart'; - -UiFactory TextField = uiFunction((_) {}), _$TextFieldConfig); - -@Props(keyNamespace: '') -mixin TextFieldProps on UiProps { - @convertJsMapProp - Map? sx; - - @Deprecated('Deprecated, but not the same as the system props color') - dynamic color; -} diff --git a/test/test_fixtures/mui_stub_project/lib/components/typography.dart b/test/test_fixtures/mui_stub_project/lib/components/typography.dart deleted file mode 100644 index f43990df..00000000 --- a/test/test_fixtures/mui_stub_project/lib/components/typography.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:over_react/over_react.dart'; - -part 'typography.over_react.g.dart'; - -UiFactory Typography = uiFunction((_) {}), _$TypographyConfig); - -@Props(keyNamespace: '') -mixin TypographyProps on UiProps { - @convertJsMapProp - Map? sx; - - @Deprecated('Use sx.') dynamic alignContent; - @Deprecated('Use sx.') dynamic alignItems; - @Deprecated('Use sx.') dynamic alignSelf; - @Deprecated('Use sx.') dynamic bgcolor; - @Deprecated('Use sx.') dynamic border; - @Deprecated('Use sx.') dynamic borderBottom; - @Deprecated('Use sx.') dynamic borderColor; - @Deprecated('Use sx.') dynamic borderLeft; - @Deprecated('Use sx.') dynamic borderRadius; - @Deprecated('Use sx.') dynamic borderRight; - @Deprecated('Use sx.') dynamic borderTop; - @Deprecated('Use sx.') dynamic bottom; - @Deprecated('Use sx.') dynamic boxShadow; - @Deprecated('Use sx.') dynamic boxSizing; - @Deprecated('Use sx.') dynamic color; - @Deprecated('Use sx.') dynamic columnGap; - @Deprecated('Use sx.') dynamic display; - @Deprecated('Use sx.') dynamic displayPrint; - @Deprecated('Use sx.') dynamic flex; - @Deprecated('Use sx.') dynamic flexBasis; - @Deprecated('Use sx.') dynamic flexDirection; - @Deprecated('Use sx.') dynamic flexGrow; - @Deprecated('Use sx.') dynamic flexShrink; - @Deprecated('Use sx.') dynamic flexWrap; - @Deprecated('Use sx.') dynamic fontFamily; - @Deprecated('Use sx.') dynamic fontSize; - @Deprecated('Use sx.') dynamic fontStyle; - @Deprecated('Use sx.') dynamic fontWeight; - @Deprecated('Use sx.') dynamic gap; - @Deprecated('Use sx.') dynamic gridArea; - @Deprecated('Use sx.') dynamic gridAutoColumns; - @Deprecated('Use sx.') dynamic gridAutoFlow; - @Deprecated('Use sx.') dynamic gridAutoRows; - @Deprecated('Use sx.') dynamic gridColumn; - @Deprecated('Use sx.') dynamic gridRow; - @Deprecated('Use sx.') dynamic gridTemplateAreas; - @Deprecated('Use sx.') dynamic gridTemplateColumns; - @Deprecated('Use sx.') dynamic gridTemplateRows; - @Deprecated('Use sx.') dynamic height; - @Deprecated('Use sx.') dynamic justifyContent; - @Deprecated('Use sx.') dynamic justifyItems; - @Deprecated('Use sx.') dynamic justifySelf; - @Deprecated('Use sx.') dynamic left; - @Deprecated('Use sx.') dynamic letterSpacing; - @Deprecated('Use sx.') dynamic lineHeight; - @Deprecated('Use sx.') dynamic m; - @Deprecated('Use sx.') dynamic margin; - @Deprecated('Use sx.') dynamic marginBlock; - @Deprecated('Use sx.') dynamic marginBlockEnd; - @Deprecated('Use sx.') dynamic marginBlockStart; - @Deprecated('Use sx.') dynamic marginBottom; - @Deprecated('Use sx.') dynamic marginInline; - @Deprecated('Use sx.') dynamic marginInlineEnd; - @Deprecated('Use sx.') dynamic marginInlineStart; - @Deprecated('Use sx.') dynamic marginLeft; - @Deprecated('Use sx.') dynamic marginRight; - @Deprecated('Use sx.') dynamic marginTop; - @Deprecated('Use sx.') dynamic marginX; - @Deprecated('Use sx.') dynamic marginY; - @Deprecated('Use sx.') dynamic maxHeight; - @Deprecated('Use sx.') dynamic maxWidth; - @Deprecated('Use sx.') dynamic mb; - @Deprecated('Use sx.') dynamic minHeight; - @Deprecated('Use sx.') dynamic minWidth; - @Deprecated('Use sx.') dynamic ml; - @Deprecated('Use sx.') dynamic mr; - @Deprecated('Use sx.') dynamic mt; - @Deprecated('Use sx.') dynamic mx; - @Deprecated('Use sx.') dynamic my; - @Deprecated('Use sx.') dynamic order; - @Deprecated('Use sx.') dynamic overflow; - @Deprecated('Use sx.') dynamic p; - @Deprecated('Use sx.') dynamic padding; - @Deprecated('Use sx.') dynamic paddingBlock; - @Deprecated('Use sx.') dynamic paddingBlockEnd; - @Deprecated('Use sx.') dynamic paddingBlockStart; - @Deprecated('Use sx.') dynamic paddingBottom; - @Deprecated('Use sx.') dynamic paddingInline; - @Deprecated('Use sx.') dynamic paddingInlineEnd; - @Deprecated('Use sx.') dynamic paddingInlineStart; - @Deprecated('Use sx.') dynamic paddingLeft; - @Deprecated('Use sx.') dynamic paddingRight; - @Deprecated('Use sx.') dynamic paddingTop; - @Deprecated('Use sx.') dynamic paddingX; - @Deprecated('Use sx.') dynamic paddingY; - @Deprecated('Use sx.') dynamic pb; - @Deprecated('Use sx.') dynamic pl; - @Deprecated('Use sx.') dynamic position; - @Deprecated('Use sx.') dynamic pr; - @Deprecated('Use sx.') dynamic pt; - @Deprecated('Use sx.') dynamic px; - @Deprecated('Use sx.') dynamic py; - @Deprecated('Use sx.') dynamic right; - @Deprecated('Use sx.') dynamic rowGap; - @Deprecated('Use sx.') dynamic textAlign; - @Deprecated('Use sx.') dynamic textOverflow; - @Deprecated('Use sx.') dynamic textTransform; - @Deprecated('Use sx.') dynamic top; - @Deprecated('Use sx.') dynamic typography; - @Deprecated('Use sx.') dynamic visibility; - @Deprecated('Use sx.') dynamic whiteSpace; - @Deprecated('Use sx.') dynamic width; - @Deprecated('Use sx.') dynamic zIndex; -} diff --git a/test/test_fixtures/mui_stub_project/pubspec.yaml b/test/test_fixtures/mui_stub_project/pubspec.yaml deleted file mode 100644 index 5e899d67..00000000 --- a/test/test_fixtures/mui_stub_project/pubspec.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: mui_stub -environment: - sdk: '>=2.19.0 <3.0.0' -dependencies: - over_react: ^5.6.0 From 980279981b6f355a3a62bac5aef8d6587d4e5194 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 19:29:09 -0700 Subject: [PATCH 38/43] Switch from component name list to duck-typing system props --- .../system_props_to_sx_migrator.dart | 42 ++++++++++------ .../system_props_to_sx_migrator_test.dart | 49 ++++++++++++++++++- 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 62dbc73e..c2f430bf 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -14,9 +14,11 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/util/component_usage.dart'; import 'package:over_react_codemod/src/util/component_usage_migrator.dart'; @@ -112,15 +114,13 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (propName == 'sx') { existingSxProp = prop; } else if (systemPropNames.contains(propName)) { - final propElement = prop.staticElement?.nonSynthetic; - final declaringPropsElement = propElement?.enclosingElement; - if (propElement != null && declaringPropsElement != null) { - final componentName = declaringPropsElement.name - ?.replaceAll(RegExp(r'Props(Mixin)?$'), ''); - if (_componentsWithDeprecatedSystemProps.contains(componentName) && - propElement.hasDeprecated) { - systemProps.add(prop); - } + final isPropDeprecated = + prop.staticElement?.nonSynthetic.hasDeprecated ?? false; + late final propsClassElement = usage.propsClassElement; + if (isPropDeprecated && + propsClassElement != null && + hasSxAndSomeSystemProps(propsClassElement)) { + systemProps.add(prop); } } } @@ -356,13 +356,25 @@ List<_PropSpreadSource> _detectPropForwardingSources( .toList(); } -const _componentsWithDeprecatedSystemProps = { - 'Box', - 'Grid', - 'Stack', - 'Typography', -}; +/// Duck-types a component props class as a MUI-system-props-supporting props +/// class by checking for the presence of an `sx` prop and at least one system prop. +@visibleForTesting +bool hasSxAndSomeSystemProps(InterfaceElement propsElement) { + const propsToCheck = [ + 'sx', + // The following items are arbitrary; we don't need to check for all system props, + // just a few to help prevent false positives where components have a prop or two + // that happens to match a system prop. + 'bgcolor', + 'm', + 'letterSpacing', + ]; + + return propsToCheck.every((propName) => + propsElement.lookUpGetter(propName, propsElement.library) != null); +} +@visibleForTesting const systemPropNames = { 'm', 'mt', diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 900817a2..35ebf299 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -14,6 +14,9 @@ import 'dart:convert'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:collection/collection.dart'; import 'package:over_react_codemod/src/mui_suggestors/system_props_to_sx_migrator.dart'; import 'package:test/test.dart'; @@ -57,6 +60,36 @@ void main() { resolvedContext: resolvedContext, ); + group('hasSxAndSomeSystemProps returns as expected for test props:', () { + late ResolvedUnitResult unit; + + setUpAll(() async { + final file = + await resolvedContext.resolvedFileContextForTest(withHeader('')); + unit = (await file.getResolvedUnit())!; + }); + + InterfaceElement getProps(String propsName) => + getImportedInterfaceElement(unit, propsName); + + test('with system props', () async { + expect( + hasSxAndSomeSystemProps(getProps('BoxProps')), + isTrue, + ); + expect(hasSxAndSomeSystemProps(getProps('GridProps')), isTrue); + expect(hasSxAndSomeSystemProps(getProps('StackProps')), isTrue); + expect(hasSxAndSomeSystemProps(getProps('TypographyProps')), isTrue); + }); + + test('without system props', () async { + // Test props with sx and a prop named like a system prop. + expect(hasSxAndSomeSystemProps(getProps('TextFieldProps')), isFalse); + // Some other props from over_react. + expect(hasSxAndSomeSystemProps(getProps('DomProps')), isFalse); + }); + }); + test('migrates single system prop to sx', () async { await testSuggestor( input: withHeader(''' @@ -1156,8 +1189,8 @@ void main() { String getStubMuiLibrarySource({required String filenameWithoutExtension}) { final systemPropComponentsSource = [ 'Box', - 'Stack', 'Grid', + 'Stack', 'Typography', ].map((componentName) { return ''' @@ -1195,3 +1228,17 @@ String getStubMuiLibrarySource({required String filenameWithoutExtension}) { } '''; } + +// Borrowed from https://github.com/Workiva/over_react/blob/5.6.0/tools/analyzer_plugin/test/unit/util/prop_declaration/util.dart#L73-L78 + +InterfaceElement getInterfaceElement(ResolvedUnitResult result, String name) => + result.libraryElement.topLevelElements + .whereType() + .singleWhere((e) => e.name == name); + +InterfaceElement getImportedInterfaceElement( + ResolvedUnitResult result, String name) => + result.libraryElement.importedLibraries + .map((l) => l.exportNamespace.get(name)) + .whereNotNull() + .single as InterfaceElement; From 7e9d28e369060546c5172c1bc080504c34278405 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Tue, 24 Feb 2026 19:34:29 -0700 Subject: [PATCH 39/43] Update test util name --- .../system_props_to_sx_migrator_test.dart | 200 +++++++++--------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 35ebf299..0892383e 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -41,7 +41,7 @@ void main() { muiUri = Uri.file(muiFile.path).toString(); }); - String withHeader(String source) => ''' + String withImports(String source) => ''' //@dart=2.19 import 'package:over_react/over_react.dart'; import ${jsonEncode(muiUri.toString())}; @@ -65,7 +65,7 @@ void main() { setUpAll(() async { final file = - await resolvedContext.resolvedFileContextForTest(withHeader('')); + await resolvedContext.resolvedFileContextForTest(withImports('')); unit = (await file.getResolvedUnit())!; }); @@ -92,11 +92,11 @@ void main() { test('migrates single system prop to sx', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box()..mt = 2)(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box()..sx = {'mt': 2})(); '''), @@ -105,7 +105,7 @@ void main() { test('migrates multiple system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..mt = 2 @@ -113,7 +113,7 @@ void main() { ..bgcolor = 'primary.main' )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -129,14 +129,14 @@ void main() { group('merges with existing sx map literal', () { test('without trailing commas', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..sx = {'border': '1px solid black'} ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -150,7 +150,7 @@ void main() { test('with trailing commas', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..sx = { @@ -159,7 +159,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -173,14 +173,14 @@ void main() { test('that is empty', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..sx = {} ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -193,7 +193,7 @@ void main() { test('with multiple existing entries', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..sx = { @@ -203,7 +203,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -220,7 +220,7 @@ void main() { group('merges with forwarded sx prop using spread:', () { test('nullable', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..sx = getSx() @@ -228,7 +228,7 @@ void main() { )(); Map? getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..sx = { @@ -243,7 +243,7 @@ void main() { test('non-nullable', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..sx = getSx() @@ -251,7 +251,7 @@ void main() { )(); Map getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..sx = {'mt': 2, ...getSx()} @@ -263,7 +263,7 @@ void main() { test('dynamic', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..sx = getSx() @@ -271,7 +271,7 @@ void main() { )(); dynamic getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..sx = { @@ -289,14 +289,14 @@ void main() { group('forwarded with', () { test('addProps', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..addProps(props) ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -308,14 +308,14 @@ void main() { test('addAll', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..addAll(props) ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addAll(props) @@ -327,14 +327,14 @@ void main() { test('getPropsToForward', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..addProps(props.getPropsToForward()) ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props.getPropsToForward()) @@ -346,14 +346,14 @@ void main() { test('addPropsToForward', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..modifyProps(props.addPropsToForward()) ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..modifyProps(props.addPropsToForward()) @@ -368,7 +368,7 @@ void main() { test('even when there are other unrelated calls in the cascade', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -376,7 +376,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -391,14 +391,14 @@ void main() { group('adds FIXME comment when forwarding is ambiguous:', () { test('modifyProps with unknown function', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..modifyProps((_) {}) ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..modifyProps((_) {}) @@ -414,7 +414,7 @@ void main() { test('copyUnconsumedProps', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' abstract class FooComponent extends UiComponent2 { content(BoxProps props) => (Box() @@ -423,7 +423,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' abstract class FooComponent extends UiComponent2 { content(BoxProps props) => (Box() @@ -442,7 +442,7 @@ void main() { group('generic props:', () { test('Map', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(Map props) { (Box() ..addAll(props) @@ -450,7 +450,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(Map props) { (Box() ..addAll(props) @@ -467,7 +467,7 @@ void main() { test('UiProps', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(UiProps props) { (Box() ..addAll(props) @@ -475,7 +475,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(UiProps props) { (Box() ..addAll(props) @@ -492,7 +492,7 @@ void main() { test('dynamic', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(dynamic props) { (Box() ..addAll(props) @@ -500,7 +500,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(dynamic props) { (Box() ..addAll(props) @@ -518,7 +518,7 @@ void main() { test('multiple prop forwarding calls', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props, BoxProps props2) { (Box() ..addProps(props) @@ -527,7 +527,7 @@ void main() { )(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props, BoxProps props2) { (Box() ..addProps(props) @@ -546,7 +546,7 @@ void main() { test('handles complex prop values with expressions', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(bool condition) { (Box() ..mt = condition ? 2 : 4 @@ -555,7 +555,7 @@ void main() { } int getSpacing() => 3; '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(bool condition) { (Box() ..sx = { @@ -571,13 +571,13 @@ void main() { test('handles responsive system prop values', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..mt = {'xs': 1, 'sm': 2, 'md': 3} )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = {'mt': {'xs': 1, 'sm': 2, 'md': 3}} @@ -588,7 +588,7 @@ void main() { test('preserves non-system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..id = 'test' @@ -597,7 +597,7 @@ void main() { ..onClick = (_) {} )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..id = 'test' @@ -611,13 +611,13 @@ void main() { test('handles multiple components in the same file', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() { (Box()..mt = 2)(); (Box()..p = 3)(); } '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() { (Box()..sx = {'mt': 2})(); (Box()..sx = {'p': 3})(); @@ -628,7 +628,7 @@ void main() { test('respects orcm_ignore comments', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' // orcm_ignore content() => (Box()..mt = 2)(); '''), @@ -638,7 +638,7 @@ void main() { test('does not migrate components without deprecated system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..id = 'test' @@ -651,7 +651,7 @@ void main() { test('does not migrate deprecated props with the same name as system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (TextField()..color = '')(); '''), ); @@ -659,7 +659,7 @@ void main() { test('does not flag unrelated cascades with FIXMEs', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..addProp('foo', 'bar') @@ -675,7 +675,7 @@ void main() { () { test('an existing sx map literal', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..mt = 2 @@ -683,7 +683,7 @@ void main() { ..sx = {'border': '1px solid black'} )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -699,7 +699,7 @@ void main() { test('an existing sx value', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..mt = 2 @@ -708,7 +708,7 @@ void main() { )(); Map getSx() => {'color': 'red'}; '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -725,14 +725,14 @@ void main() { test('no existing sx', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..mt = 2 ..addProps(props) )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -751,7 +751,7 @@ void main() { test('inserts after prop forwarding to avoid being overwritten', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..mt = 2 @@ -759,7 +759,7 @@ void main() { ..id = 'test' )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -774,7 +774,7 @@ void main() { test('inserts at location of last system prop when no prop forwarding', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..id = 'first' @@ -784,7 +784,7 @@ void main() { ..onClick = (_) {} )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..id = 'first' @@ -799,7 +799,7 @@ void main() { test('inserts after latest of (forwarding or last system prop)', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..mt = 2 @@ -807,7 +807,7 @@ void main() { ..p = 3 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -821,7 +821,7 @@ void main() { test('inserts after all forwarding calls when multiple exist', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props1, BoxProps props2) => (Box() ..addProps(props1) @@ -830,7 +830,7 @@ void main() { ..addProps(props2) )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props1, BoxProps props2) => (Box() ..addProps(props1) @@ -847,14 +847,14 @@ void main() { group('multiline formatting:', () { test('uses single line for short sx maps (< 3 elements)', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..mt = 2 ..p = 3 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = {'mt': 2, 'p': 3} @@ -865,7 +865,7 @@ void main() { test('uses multiline for 3+ elements', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..mt = 2 @@ -873,7 +873,7 @@ void main() { ..mb = 4 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -889,14 +889,14 @@ void main() { test('uses multiline for long content (>= 20 chars with 2+ elements)', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..mt = 2 ..bgcolor = 'verylongcolor.main' )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -912,14 +912,14 @@ void main() { group('preserves comments before system props:', () { test('single line comment before single system prop', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() // Add margin ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -933,7 +933,7 @@ void main() { test('single line comment before one of multiple system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..mt = 2 @@ -941,7 +941,7 @@ void main() { ..p = 3 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -956,7 +956,7 @@ void main() { test('multiple comments before different system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() // Top margin @@ -967,7 +967,7 @@ void main() { ..bgcolor = 'blue' )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -985,7 +985,7 @@ void main() { test('multi-line comment before system prop', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() /* This is a longer comment @@ -993,7 +993,7 @@ void main() { ..mt = 2 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -1008,7 +1008,7 @@ void main() { test('comment before system prop with existing sx', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..sx = {'border': '1px solid black'} @@ -1018,7 +1018,7 @@ void main() { '''), // Not sure why dartfmt allows two entries like this with trailing commas // on the same line, but it is what it is. - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..sx = { @@ -1033,7 +1033,7 @@ void main() { test('comments before system props mixed with non-system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..id = 'test' @@ -1044,7 +1044,7 @@ void main() { ..p = 3 )(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() ..id = 'test' @@ -1062,7 +1062,7 @@ void main() { test('comment before system prop with forwarding', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -1072,7 +1072,7 @@ void main() { '''), // Not sure why dartfmt allows two entries like this with trailing commas // on the same line, but it is what it is. - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content(BoxProps props) => (Box() ..addProps(props) @@ -1088,7 +1088,7 @@ void main() { test('inline comment on same line as system prop', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() ..mt = 2 // top margin @@ -1098,7 +1098,7 @@ void main() { // Inline comment behavior here isn't great, but it's too much effort // to deal with in this codemod. // Just verify the codemod doesn't break or do anything too outlandish. - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() // top margin @@ -1110,7 +1110,7 @@ void main() { test('multiple comment types with system props', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box() // Line comment @@ -1122,7 +1122,7 @@ void main() { // Inline comment behavior here isn't great, but it's too much effort // to deal with in this codemod. // Just verify the codemod doesn't break or do anything too outlandish. - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box() // inline comment @@ -1141,10 +1141,10 @@ void main() { group('handles different component types with system props:', () { test('Box', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Box()..mt = 2)(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Box()..sx = {'mt': 2})(); '''), ); @@ -1152,10 +1152,10 @@ void main() { test('Grid', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Grid()..mt = 2)(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Grid()..sx = {'mt': 2})(); '''), ); @@ -1163,10 +1163,10 @@ void main() { test('Stack', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Stack()..mt = 2)(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Stack()..sx = {'mt': 2})(); '''), ); @@ -1174,10 +1174,10 @@ void main() { test('Typography', () async { await testSuggestor( - input: withHeader(''' + input: withImports(''' content() => (Typography()..mt = 2)(); '''), - expectedOutput: withHeader(''' + expectedOutput: withImports(''' content() => (Typography()..sx = {'mt': 2})(); '''), ); From e0846941d8d3f072afea6fb531c446cc0bb9ff8b Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 25 Feb 2026 14:52:20 -0700 Subject: [PATCH 40/43] Cleanup --- .../system_props_to_sx_migrator.dart | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index c2f430bf..f657019a 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -95,12 +95,6 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { @override get shouldFlagClassName => false; - bool isFirstTokenOnLine(Token token) { - final sourceFile = context.sourceFile; - final lineStart = sourceFile.getOffset(sourceFile.getLine(token.offset)); - return sourceFile.getText(lineStart, token.offset).trim().isEmpty; - } - @override void migrateUsage(FluentComponentUsage usage) { super.migrateUsage(usage); @@ -128,17 +122,14 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { // No system props to migrate; bail out for this usage. if (systemProps.isEmpty) return; + // Migrate system props to sx entries, and remove old system props. final migratedSystemPropEntries = []; for (final prop in systemProps) { - final propName = prop.name.name; - final propValue = context.sourceFile - .getText(prop.rightHandSide.offset, prop.rightHandSide.end); - // Carry over comments before the node // (we won't try to handle end-of-line comments). final beforeComments = allCommentsForNode(prop.node) // Don't include end-of-line comments that should stay with the previous line. - .skipWhile((comment) => !isFirstTokenOnLine(comment)) + .skipWhile((comment) => !_isFirstTokenOnLine(comment)) .toList(); final commentSource = beforeComments.isEmpty @@ -148,33 +139,33 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { .getText(beforeComments.first.offset, beforeComments.last.end) .trimRight(); - // Create new sx entry migratedSystemPropEntries.add([ if (commentSource.isNotEmpty) '\n $commentSource', - "'$propName': $propValue" + "'${prop.name.name}': ${context.sourceFor(prop.rightHandSide)}" ].join('\n')); - // Remove old system prop yieldPatch('', beforeComments.firstOrNull?.offset ?? prop.node.offset, prop.node.end); } final propForwardingSources = _detectPropForwardingSources(usage); - final fixmes = []; + final bool anySystemPropSetBeforeForwarding; if (propForwardingSources.isNotEmpty) { final propForwardingOffsets = propForwardingSources.map((s) => s.cascadedMethod.node.end).toList(); final systemPropOffsets = systemProps.map((p) => p.node.end).toList(); - final anySystemPropSetBeforeForwarding = + anySystemPropSetBeforeForwarding = systemPropOffsets.min < propForwardingOffsets.max; - if (anySystemPropSetBeforeForwarding) { - fixmes.add( - 'Previously, it was possible for forwarded system props to overwrite these migrated styles, but not anymore since sx takes precedence over any system props.' - '\n Double-check that this new behavior is okay.'); - } + } else { + anySystemPropSetBeforeForwarding = false; } + final fixmes = [ + if (anySystemPropSetBeforeForwarding) + 'Previously, it was possible for forwarded system props to overwrite these migrated styles, but not anymore since sx takes precedence over any system props.' + '\n Double-check that this new behavior is okay.', + ]; String getFixmesSource() { if (fixmes.isEmpty) return ''; // Add a leading newline to ensure comments don't get stuck to the previous line. @@ -185,6 +176,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { .join(''); } + // Add or update sx prop wirth migrated system props. if (existingSxProp != null) { // // Case 1: add styles to existing sx prop value @@ -239,9 +231,13 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { if (type != null && type.isPropsClass && type.element.name != 'UiProps') { + // It's a non-generic props class; check if it has `sx` prop. return type.element.lookUpGetter('sx', type.element.library) != null; + } else { + // The source props weren't available, or the expression is dynamic + // or a generic type (e.g., Map, UiProps), so we can't be sure it doesn't have sx. + return true; } - return true; }); if (mightForwardSx) { @@ -290,6 +286,12 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { } } + bool _isFirstTokenOnLine(Token token) { + final sourceFile = context.sourceFile; + final lineStart = sourceFile.getOffset(sourceFile.getLine(token.offset)); + return sourceFile.getText(lineStart, token.offset).trim().isEmpty; + } + static bool _shouldForceMultiline(List mapElements) => // Force multiline if there are a certain number of entries, or... mapElements.length >= 3 || From a90574021effb01e9604a1e8c52e4a10e1a3e49d Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Wed, 25 Feb 2026 17:06:19 -0700 Subject: [PATCH 41/43] Reorganize system props code, improve doc comments --- lib/src/mui_suggestors/system_props.dart | 139 ++++++++++++++++++ .../system_props_to_sx_migrator.dart | 129 +--------------- .../system_props_to_sx_migrator_test.dart | 1 + 3 files changed, 142 insertions(+), 127 deletions(-) create mode 100644 lib/src/mui_suggestors/system_props.dart diff --git a/lib/src/mui_suggestors/system_props.dart b/lib/src/mui_suggestors/system_props.dart new file mode 100644 index 00000000..4d8ff96b --- /dev/null +++ b/lib/src/mui_suggestors/system_props.dart @@ -0,0 +1,139 @@ +// Copyright 2026 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/element/element.dart'; + +/// Duck-types a component props class as a MUI-system-props-supporting props +/// class by checking for the presence of an `sx` prop and at least one system prop. +bool hasSxAndSomeSystemProps(InterfaceElement propsElement) { + const propsToCheck = [ + 'sx', + // The following items are arbitrary; we don't need to check for all system props, + // just a few to help prevent false positives where components have a prop or two + // that happens to match a system prop. + 'bgcolor', + 'm', + 'letterSpacing', + ]; + + return propsToCheck.every((propName) => + propsElement.lookUpGetter(propName, propsElement.library) != null); +} + +/// The names of all the MUI system props. +const systemPropNames = { + 'm', + 'mt', + 'mr', + 'mb', + 'ml', + 'mx', + 'my', + 'p', + 'pt', + 'pr', + 'pb', + 'pl', + 'px', + 'py', + 'width', + 'maxWidth', + 'minWidth', + 'height', + 'maxHeight', + 'minHeight', + 'boxSizing', + 'display', + 'displayPrint', + 'overflow', + 'textOverflow', + 'visibility', + 'whiteSpace', + 'flexBasis', + 'flexDirection', + 'flexWrap', + 'justifyContent', + 'alignItems', + 'alignContent', + 'order', + 'flex', + 'flexGrow', + 'flexShrink', + 'alignSelf', + 'justifyItems', + 'justifySelf', + 'gap', + 'columnGap', + 'rowGap', + 'gridColumn', + 'gridRow', + 'gridAutoFlow', + 'gridAutoColumns', + 'gridAutoRows', + 'gridTemplateColumns', + 'gridTemplateRows', + 'gridTemplateAreas', + 'gridArea', + 'bgcolor', + 'color', + 'zIndex', + 'position', + 'top', + 'right', + 'bottom', + 'left', + 'boxShadow', + 'border', + 'borderTop', + 'borderRight', + 'borderBottom', + 'borderLeft', + 'borderColor', + 'borderRadius', + 'fontFamily', + 'fontSize', + 'fontStyle', + 'fontWeight', + 'letterSpacing', + 'lineHeight', + 'textAlign', + 'textTransform', + 'margin', + 'marginTop', + 'marginRight', + 'marginBottom', + 'marginLeft', + 'marginX', + 'marginY', + 'marginInline', + 'marginInlineStart', + 'marginInlineEnd', + 'marginBlock', + 'marginBlockStart', + 'marginBlockEnd', + 'padding', + 'paddingTop', + 'paddingRight', + 'paddingBottom', + 'paddingLeft', + 'paddingX', + 'paddingY', + 'paddingInline', + 'paddingInlineStart', + 'paddingInlineEnd', + 'paddingBlock', + 'paddingBlockStart', + 'paddingBlockEnd', + 'typography', +}; diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index f657019a..90056b62 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -14,16 +14,16 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; -import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/util/component_usage.dart'; import 'package:over_react_codemod/src/util/component_usage_migrator.dart'; import 'package:over_react_codemod/src/util/element_type_helpers.dart'; +import 'system_props.dart'; + /// A suggestor that migrates deprecated MUI system props to the `sx` prop, /// ensuring that existing `sx` prop values are preserved and merged correctly. /// @@ -357,128 +357,3 @@ List<_PropSpreadSource> _detectPropForwardingSources( .whereNotNull() .toList(); } - -/// Duck-types a component props class as a MUI-system-props-supporting props -/// class by checking for the presence of an `sx` prop and at least one system prop. -@visibleForTesting -bool hasSxAndSomeSystemProps(InterfaceElement propsElement) { - const propsToCheck = [ - 'sx', - // The following items are arbitrary; we don't need to check for all system props, - // just a few to help prevent false positives where components have a prop or two - // that happens to match a system prop. - 'bgcolor', - 'm', - 'letterSpacing', - ]; - - return propsToCheck.every((propName) => - propsElement.lookUpGetter(propName, propsElement.library) != null); -} - -@visibleForTesting -const systemPropNames = { - 'm', - 'mt', - 'mr', - 'mb', - 'ml', - 'mx', - 'my', - 'p', - 'pt', - 'pr', - 'pb', - 'pl', - 'px', - 'py', - 'width', - 'maxWidth', - 'minWidth', - 'height', - 'maxHeight', - 'minHeight', - 'boxSizing', - 'display', - 'displayPrint', - 'overflow', - 'textOverflow', - 'visibility', - 'whiteSpace', - 'flexBasis', - 'flexDirection', - 'flexWrap', - 'justifyContent', - 'alignItems', - 'alignContent', - 'order', - 'flex', - 'flexGrow', - 'flexShrink', - 'alignSelf', - 'justifyItems', - 'justifySelf', - 'gap', - 'columnGap', - 'rowGap', - 'gridColumn', - 'gridRow', - 'gridAutoFlow', - 'gridAutoColumns', - 'gridAutoRows', - 'gridTemplateColumns', - 'gridTemplateRows', - 'gridTemplateAreas', - 'gridArea', - 'bgcolor', - 'color', - 'zIndex', - 'position', - 'top', - 'right', - 'bottom', - 'left', - 'boxShadow', - 'border', - 'borderTop', - 'borderRight', - 'borderBottom', - 'borderLeft', - 'borderColor', - 'borderRadius', - 'fontFamily', - 'fontSize', - 'fontStyle', - 'fontWeight', - 'letterSpacing', - 'lineHeight', - 'textAlign', - 'textTransform', - 'margin', - 'marginTop', - 'marginRight', - 'marginBottom', - 'marginLeft', - 'marginX', - 'marginY', - 'marginInline', - 'marginInlineStart', - 'marginInlineEnd', - 'marginBlock', - 'marginBlockStart', - 'marginBlockEnd', - 'padding', - 'paddingTop', - 'paddingRight', - 'paddingBottom', - 'paddingLeft', - 'paddingX', - 'paddingY', - 'paddingInline', - 'paddingInlineStart', - 'paddingInlineEnd', - 'paddingBlock', - 'paddingBlockStart', - 'paddingBlockEnd', - 'typography', -}; diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 0892383e..caf81e46 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -17,6 +17,7 @@ import 'dart:convert'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:collection/collection.dart'; +import 'package:over_react_codemod/src/mui_suggestors/system_props.dart'; import 'package:over_react_codemod/src/mui_suggestors/system_props_to_sx_migrator.dart'; import 'package:test/test.dart'; From def2266e0a6cbe7b1f912a3ca534272af940e294 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 26 Feb 2026 12:43:16 -0700 Subject: [PATCH 42/43] Address CR feedback --- .../system_props_to_sx_migrator.dart | 2 +- .../system_props_to_sx_migrator_test.dart | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 90056b62..14cfa404 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -176,7 +176,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { .join(''); } - // Add or update sx prop wirth migrated system props. + // Add or update sx prop with migrated system props. if (existingSxProp != null) { // // Case 1: add styles to existing sx prop value diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index caf81e46..007daf2f 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -216,6 +216,34 @@ void main() { '''), ); }); + + test('with an entry that has the same key as a migrated system prop', + () async { + + await testSuggestor( + input: withImports(''' + content() => + (Box() + ..sx = { + 'mt': 1, + } + ..mt = 2 + )(); + '''), + // This will result in duplicate keys in the map, but they're in the + // correct order to preserve the original behavior, and will result in + // a hint that there are duplicate entries. + expectedOutput: withImports(''' + content() => + (Box() + ..sx = { + 'mt': 2, + 'mt': 1, + } + )(); + '''), + ); + }); }); group('merges with forwarded sx prop using spread:', () { From a0f849866a7fa822a825ce15f9dd80f4cea1413a Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 26 Feb 2026 13:49:52 -0700 Subject: [PATCH 43/43] Format --- test/mui_suggestors/system_props_to_sx_migrator_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mui_suggestors/system_props_to_sx_migrator_test.dart b/test/mui_suggestors/system_props_to_sx_migrator_test.dart index 007daf2f..29f794f8 100644 --- a/test/mui_suggestors/system_props_to_sx_migrator_test.dart +++ b/test/mui_suggestors/system_props_to_sx_migrator_test.dart @@ -219,7 +219,6 @@ void main() { test('with an entry that has the same key as a migrated system prop', () async { - await testSuggestor( input: withImports(''' content() =>