Skip to content

Commit 9802799

Browse files
Switch from component name list to duck-typing system props
1 parent 2f81b79 commit 9802799

File tree

2 files changed

+75
-16
lines changed

2 files changed

+75
-16
lines changed

lib/src/mui_suggestors/system_props_to_sx_migrator.dart

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
import 'package:analyzer/dart/ast/ast.dart';
1616
import 'package:analyzer/dart/ast/token.dart';
17+
import 'package:analyzer/dart/element/element.dart';
1718
import 'package:analyzer/dart/element/nullability_suffix.dart';
1819
import 'package:analyzer/dart/element/type.dart';
1920
import 'package:collection/collection.dart';
21+
import 'package:meta/meta.dart';
2022
import 'package:over_react_codemod/src/util.dart';
2123
import 'package:over_react_codemod/src/util/component_usage.dart';
2224
import 'package:over_react_codemod/src/util/component_usage_migrator.dart';
@@ -112,15 +114,13 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator {
112114
if (propName == 'sx') {
113115
existingSxProp = prop;
114116
} else if (systemPropNames.contains(propName)) {
115-
final propElement = prop.staticElement?.nonSynthetic;
116-
final declaringPropsElement = propElement?.enclosingElement;
117-
if (propElement != null && declaringPropsElement != null) {
118-
final componentName = declaringPropsElement.name
119-
?.replaceAll(RegExp(r'Props(Mixin)?$'), '');
120-
if (_componentsWithDeprecatedSystemProps.contains(componentName) &&
121-
propElement.hasDeprecated) {
122-
systemProps.add(prop);
123-
}
117+
final isPropDeprecated =
118+
prop.staticElement?.nonSynthetic.hasDeprecated ?? false;
119+
late final propsClassElement = usage.propsClassElement;
120+
if (isPropDeprecated &&
121+
propsClassElement != null &&
122+
hasSxAndSomeSystemProps(propsClassElement)) {
123+
systemProps.add(prop);
124124
}
125125
}
126126
}
@@ -356,13 +356,25 @@ List<_PropSpreadSource> _detectPropForwardingSources(
356356
.toList();
357357
}
358358

359-
const _componentsWithDeprecatedSystemProps = {
360-
'Box',
361-
'Grid',
362-
'Stack',
363-
'Typography',
364-
};
359+
/// Duck-types a component props class as a MUI-system-props-supporting props
360+
/// class by checking for the presence of an `sx` prop and at least one system prop.
361+
@visibleForTesting
362+
bool hasSxAndSomeSystemProps(InterfaceElement propsElement) {
363+
const propsToCheck = [
364+
'sx',
365+
// The following items are arbitrary; we don't need to check for all system props,
366+
// just a few to help prevent false positives where components have a prop or two
367+
// that happens to match a system prop.
368+
'bgcolor',
369+
'm',
370+
'letterSpacing',
371+
];
372+
373+
return propsToCheck.every((propName) =>
374+
propsElement.lookUpGetter(propName, propsElement.library) != null);
375+
}
365376

377+
@visibleForTesting
366378
const systemPropNames = {
367379
'm',
368380
'mt',

test/mui_suggestors/system_props_to_sx_migrator_test.dart

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
import 'dart:convert';
1616

17+
import 'package:analyzer/dart/analysis/results.dart';
18+
import 'package:analyzer/dart/element/element.dart';
19+
import 'package:collection/collection.dart';
1720
import 'package:over_react_codemod/src/mui_suggestors/system_props_to_sx_migrator.dart';
1821
import 'package:test/test.dart';
1922

@@ -57,6 +60,36 @@ void main() {
5760
resolvedContext: resolvedContext,
5861
);
5962

63+
group('hasSxAndSomeSystemProps returns as expected for test props:', () {
64+
late ResolvedUnitResult unit;
65+
66+
setUpAll(() async {
67+
final file =
68+
await resolvedContext.resolvedFileContextForTest(withHeader(''));
69+
unit = (await file.getResolvedUnit())!;
70+
});
71+
72+
InterfaceElement getProps(String propsName) =>
73+
getImportedInterfaceElement(unit, propsName);
74+
75+
test('with system props', () async {
76+
expect(
77+
hasSxAndSomeSystemProps(getProps('BoxProps')),
78+
isTrue,
79+
);
80+
expect(hasSxAndSomeSystemProps(getProps('GridProps')), isTrue);
81+
expect(hasSxAndSomeSystemProps(getProps('StackProps')), isTrue);
82+
expect(hasSxAndSomeSystemProps(getProps('TypographyProps')), isTrue);
83+
});
84+
85+
test('without system props', () async {
86+
// Test props with sx and a prop named like a system prop.
87+
expect(hasSxAndSomeSystemProps(getProps('TextFieldProps')), isFalse);
88+
// Some other props from over_react.
89+
expect(hasSxAndSomeSystemProps(getProps('DomProps')), isFalse);
90+
});
91+
});
92+
6093
test('migrates single system prop to sx', () async {
6194
await testSuggestor(
6295
input: withHeader('''
@@ -1156,8 +1189,8 @@ void main() {
11561189
String getStubMuiLibrarySource({required String filenameWithoutExtension}) {
11571190
final systemPropComponentsSource = [
11581191
'Box',
1159-
'Stack',
11601192
'Grid',
1193+
'Stack',
11611194
'Typography',
11621195
].map((componentName) {
11631196
return '''
@@ -1195,3 +1228,17 @@ String getStubMuiLibrarySource({required String filenameWithoutExtension}) {
11951228
}
11961229
''';
11971230
}
1231+
1232+
// Borrowed from https://github.com/Workiva/over_react/blob/5.6.0/tools/analyzer_plugin/test/unit/util/prop_declaration/util.dart#L73-L78
1233+
1234+
InterfaceElement getInterfaceElement(ResolvedUnitResult result, String name) =>
1235+
result.libraryElement.topLevelElements
1236+
.whereType<InterfaceElement>()
1237+
.singleWhere((e) => e.name == name);
1238+
1239+
InterfaceElement getImportedInterfaceElement(
1240+
ResolvedUnitResult result, String name) =>
1241+
result.libraryElement.importedLibraries
1242+
.map((l) => l.exportNamespace.get(name))
1243+
.whereNotNull()
1244+
.single as InterfaceElement;

0 commit comments

Comments
 (0)