44
55import 'package:analysis_server/src/services/correction/fix.dart' ;
66import 'package:analysis_server_plugin/edit/dart/correction_producer.dart' ;
7+ import 'package:analyzer/dart/ast/token.dart' ;
78import 'package:analyzer/dart/element/element.dart' ;
89import 'package:analyzer/src/dart/ast/ast.dart' ;
910import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart' ;
@@ -36,52 +37,104 @@ class ConvertNullCheckToNullAwareElementOrEntry
3637 )) {
3738 if (node.caseClause == null ) {
3839 // An element or entry of the form `if (x != null) ...`.
39- if (thenElement is ! MapLiteralEntry ) {
40- // In case of a list or set element, we simply replace the entire
41- // element with the then-element prefixed by '?'.
40+ if (thenElement is SimpleIdentifier ) {
41+ // In case of a list or set element with a promotable target, we
42+ // simply replace the entire element with the then-element prefixed by
43+ // '?'.
4244 //
43- // `[if (x != null) x]` ==> `[?x]`
44- // `{if (x != null) x]` ==> `{?x}`
45+ // `if (x != null) x` is rewritten as `?x`
4546 await builder.addDartFileEdit (file, (builder) {
4647 builder.addSimpleReplacement (
47- range.startOffsetEndOffset (
48- node.ifKeyword.offset,
49- thenElement.offset,
50- ),
48+ range.startStart (node, thenElement),
5149 '?' ,
5250 );
5351 });
54- } else {
52+ } else if (thenElement is PostfixExpression ) {
53+ // In case of a list or set element with a getter target, we replace
54+ // the entire element with the then-element target identifier prefixed
55+ // by '?'. Note that in the case of a getter target, the null-check
56+ // operator '!' is always present in [thenElement].
57+ //
58+ // `if (x != null) x!` is rewritten as `?x`
59+ await builder.addDartFileEdit (file, (builder) {
60+ builder.addSimpleReplacement (
61+ range.startStart (node, thenElement),
62+ '?' ,
63+ );
64+ builder.addDeletion (range.endEnd (thenElement.operand, thenElement));
65+ });
66+ } else if (thenElement is MapLiteralEntry ) {
5567 // In case of a map entry we need to check if it's the key that's
5668 // promoted to non-nullable or the value.
69+ var thenElementKey = thenElement.key;
70+ var keyCanonicalElement = switch (thenElementKey) {
71+ SimpleIdentifier () => thenElementKey.canonicalElement,
72+ PostfixExpression (: var operand, operator : Token (lexeme: '!' )) =>
73+ operand.canonicalElement,
74+ _ => null ,
75+ };
76+
5777 var binaryCondition = condition as BinaryExpression ;
58- var keyCanonicalElement = thenElement.key.canonicalElement;
5978 if (keyCanonicalElement != null &&
6079 (binaryCondition.leftOperand.canonicalElement ==
6180 keyCanonicalElement ||
6281 binaryCondition.rightOperand.canonicalElement ==
6382 keyCanonicalElement)) {
64- // In case the key is promoted, we simply replace everything before
65- // the key with '?'.
66- //
67- // `{if (x != null) x: "value"}` ==> `{?x: "value"}`
68- await builder.addDartFileEdit (file, (builder) {
69- builder.addSimpleReplacement (
70- range.startOffsetEndOffset (node.offset, thenElement.key.offset),
71- '?' ,
72- );
73- });
83+ if (thenElementKey is SimpleIdentifier ) {
84+ // In case the key is null-aware and is promotable, we simply
85+ // replace everything before the key with '?'.
86+ //
87+ // `if (x != null) x: "v"` is rewritten as `?x: "v"`
88+ await builder.addDartFileEdit (file, (builder) {
89+ builder.addSimpleReplacement (
90+ range.startStart (node, thenElement.key),
91+ '?' ,
92+ );
93+ });
94+ } else if (thenElementKey is PostfixExpression ) {
95+ // In case the key is null-aware and is a getter, we replace
96+ // everything before the key with '?' and remove '!' afterwards.
97+ // Note that in the case of a getter, the null-check operator '!'
98+ // is always present in [thenElementKey].
99+ //
100+ // `if (x != null) x!: "v"` is rewritten as `?x: "v"`
101+ await builder.addDartFileEdit (file, (builder) {
102+ builder.addSimpleReplacement (
103+ range.startStart (node, thenElementKey),
104+ '?' ,
105+ );
106+ builder.addDeletion (
107+ range.endStart (thenElementKey.operand, thenElement.separator),
108+ );
109+ });
110+ }
74111 } else {
75- // In case the value is promoted, we remove everything before the
76- // key and insert '?' before the value.
77- //
78- // `{if (x != null) "key": x}` ==> `{"key": ?x}`
79- await builder.addDartFileEdit (file, (builder) {
80- builder.addDeletion (
81- range.startOffsetEndOffset (node.offset, thenElement.key.offset),
82- );
83- builder.addSimpleInsertion (thenElement.value.offset, '?' );
84- });
112+ var thenElementValue = thenElement.value;
113+ if (thenElementValue is SimpleIdentifier ) {
114+ // In case the value is null-aware and is promotable, we remove
115+ // everything before the key and insert '?' before the value.
116+ //
117+ // `if (x != null) "k": x` is rewritten as `"k": ?x`
118+ await builder.addDartFileEdit (file, (builder) {
119+ builder.addDeletion (range.startStart (node, thenElement.key));
120+ builder.addSimpleInsertion (thenElement.value.offset, '?' );
121+ });
122+ } else if (thenElementValue is PostfixExpression ) {
123+ // In case the value is null-aware and is a getter, we remove
124+ // everything before the key, insert '?' before the value, and
125+ // delete '!' after it. Note that in the case of a getter, the
126+ // null-check operator '!' is always present in
127+ // [thenElementValue].
128+ //
129+ // `if (x != null) "k": x!` is rewritten as `"k": ?x`
130+ await builder.addDartFileEdit (file, (builder) {
131+ builder.addDeletion (range.startStart (node, thenElementKey));
132+ builder.addSimpleInsertion (thenElementValue.offset, '?' );
133+ builder.addDeletion (
134+ range.endEnd (thenElementValue.operand, thenElementValue),
135+ );
136+ });
137+ }
85138 }
86139 }
87140 } else {
@@ -90,13 +143,13 @@ class ConvertNullCheckToNullAwareElementOrEntry
90143 // In case of a list or set element, we replace the entire element
91144 // with the expression to the left of 'case', prefixed by '?'.
92145 //
93- // `[if (x case var y?) y]` ==> `[?x]`
94- // `{if (x case var y?) y]` ==> `{?x}`
146+ // `if (x case var y?) y` is rewritten as `?x`
95147 await builder.addDartFileEdit (file, (builder) {
96148 builder.addSimpleReplacement (
97- range.startOffsetEndOffset (node.offset, node.end ),
98- '?${ condition . toSource ()} ' ,
149+ range.startStart (node, condition ),
150+ '?' ,
99151 );
152+ builder.addDeletion (range.endEnd (condition, node));
100153 });
101154 } else {
102155 // In case of a map entry we need to check if it's the key that's
@@ -111,28 +164,24 @@ class ConvertNullCheckToNullAwareElementOrEntry
111164 // In case the key is promoted, replace everything before ':' with
112165 // the expression before 'case', prefixed by '?'.
113166 //
114- // `{ if (x case var y?) y: "value"}` ==> `{ ?x: "value"} `
167+ // `if (x case var y?) y: "v"` is rewritten as ` ?x: "v" `
115168 await builder.addDartFileEdit (file, (builder) {
116169 builder.addSimpleReplacement (
117- range.startOffsetEndOffset (node.offset, thenElement.key.end ),
118- '?${ condition . toSource ()} ' ,
170+ range.startStart (node, condition ),
171+ '?' ,
119172 );
173+ builder.addDeletion (range.endEnd (condition, thenElement.key));
120174 });
121175 } else {
122176 // In case the value is promoted, delete everything before the key
123177 // and replace the value with the expression to the left of 'case',
124178 // prefixed by '?'.
125179 //
126- // `{ if (x case var y?) "key ": y}` ==> `{"key ": ?x} `
180+ // `if (x case var y?) "k ": y` is rewritten as `"k ": ?x`
127181 await builder.addDartFileEdit (file, (builder) {
128- builder.addDeletion (
129- range.startOffsetEndOffset (node.offset, thenElement.key.offset),
130- );
182+ builder.addDeletion (range.startStart (node, thenElement.key));
131183 builder.addSimpleReplacement (
132- range.startOffsetEndOffset (
133- thenElement.value.offset,
134- thenElement.value.end,
135- ),
184+ range.startEnd (thenElement.value, thenElement.value),
136185 '?${condition .toSource ()}' ,
137186 );
138187 });
0 commit comments