OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library services.src.correction.name_suggestion; | |
6 | |
7 import 'package:analysis_services/src/correction/strings.dart'; | |
8 import 'package:analyzer/src/generated/ast.dart'; | |
9 import 'package:analyzer/src/generated/element.dart'; | |
10 | |
11 | |
12 List<String> _KNOWN_METHOD_NAME_PREFIXES = ['get', 'is', 'to']; | |
13 | |
14 /** | |
15 * Returns a list of words for the given camel case string. | |
16 * | |
17 * 'getCamelWords' => ['get', 'Camel', 'Words'] | |
18 * 'getHTMLText' => ['get', 'HTML', 'Text'] | |
19 */ | |
20 List<String> getCamelWords(String str) { | |
21 if (str == null || str.isEmpty) { | |
22 return <String>[]; | |
23 } | |
24 List<String> parts = <String>[]; | |
25 bool wasLowerCase = false; | |
26 bool wasUpperCase = false; | |
27 int wordStart = 0; | |
28 for (int i = 0; i < str.length; i++) { | |
29 int c = str.codeUnitAt(i); | |
30 var newLowerCase = isLowerCase(c); | |
31 var newUpperCase = isUpperCase(c); | |
32 // myWord | |
33 // | ^ | |
34 if (wasLowerCase && newUpperCase) { | |
35 parts.add(str.substring(wordStart, i)); | |
36 wordStart = i; | |
37 } | |
38 // myHTMLText | |
39 // | ^ | |
40 if (wasUpperCase && | |
41 newUpperCase && | |
42 i + 1 < str.length && | |
43 isLowerCase(str.codeUnitAt(i + 1))) { | |
44 parts.add(str.substring(wordStart, i)); | |
45 wordStart = i; | |
46 } | |
47 wasLowerCase = newLowerCase; | |
48 wasUpperCase = newUpperCase; | |
49 } | |
50 parts.add(str.substring(wordStart)); | |
51 return parts; | |
52 } | |
53 | |
54 /** | |
55 * Returns possible names for a [String] variable with [text] value. | |
56 */ | |
57 List<String> getVariableNameSuggestionsForText(String text, | |
58 Set<String> excluded) { | |
59 // filter out everything except of letters and white spaces | |
60 { | |
61 StringBuffer sb = new StringBuffer(); | |
62 for (int i = 0; i < text.length; i++) { | |
63 int c = text.codeUnitAt(i); | |
64 if (isLetter(c) || isWhitespace(c)) { | |
65 sb.writeCharCode(c); | |
66 } | |
67 } | |
68 text = sb.toString(); | |
69 } | |
70 // make single camel-case text | |
71 { | |
72 List<String> words = text.split(' '); | |
73 StringBuffer sb = new StringBuffer(); | |
74 for (int i = 0; i < words.length; i++) { | |
75 String word = words[i]; | |
76 if (i > 0) { | |
77 word = capitalize(word); | |
78 } | |
79 sb.write(word); | |
80 } | |
81 text = sb.toString(); | |
82 } | |
83 // split camel-case into separate suggested names | |
84 Set<String> res = new Set(); | |
85 _addAll(excluded, res, _getCamelWordCombinations(text)); | |
86 return new List.from(res); | |
87 } | |
88 | |
89 /** | |
90 * Returns possible names for a variable with the given expected type and | |
91 * expression assigned. | |
92 */ | |
93 List<String> getVariableNameSuggestionsForExpression(DartType expectedType, | |
94 Expression assignedExpression, Set<String> excluded) { | |
95 Set<String> res = new Set(); | |
96 // use expression | |
97 if (assignedExpression != null) { | |
98 String nameFromExpression = _getBaseNameFromExpression(assignedExpression); | |
99 if (nameFromExpression != null) { | |
100 nameFromExpression = removeStart(nameFromExpression, '_'); | |
101 _addAll(excluded, res, _getCamelWordCombinations(nameFromExpression)); | |
102 } | |
103 String nameFromParent = | |
104 _getBaseNameFromLocationInParent(assignedExpression); | |
105 if (nameFromParent != null) { | |
106 _addAll(excluded, res, _getCamelWordCombinations(nameFromParent)); | |
107 } | |
108 } | |
109 // use type | |
110 if (expectedType != null && !expectedType.isDynamic) { | |
111 String typeName = expectedType.name; | |
112 if ('int' == typeName) { | |
113 _addSingleCharacterName(excluded, res, 0x69); | |
114 } else if ('double' == typeName) { | |
115 _addSingleCharacterName(excluded, res, 0x64); | |
116 } else if ('String' == typeName) { | |
117 _addSingleCharacterName(excluded, res, 0x73); | |
118 } else { | |
119 _addAll(excluded, res, _getCamelWordCombinations(typeName)); | |
120 } | |
121 res.remove(typeName); | |
122 } | |
123 // done | |
124 return new List.from(res); | |
125 } | |
126 | |
127 /** | |
128 * Adds [toAdd] items which are not excluded. | |
129 */ | |
130 void _addAll(Set<String> excluded, Set<String> result, Iterable<String> toAdd) { | |
131 for (String item in toAdd) { | |
132 // add name based on "item", but not "excluded" | |
133 for (int suffix = 1; ; suffix++) { | |
134 // prepare name, just "item" or "item2", "item3", etc | |
135 String name = item; | |
136 if (suffix > 1) { | |
137 name += suffix.toString(); | |
138 } | |
139 // add once found not excluded | |
140 if (!excluded.contains(name)) { | |
141 result.add(name); | |
142 break; | |
143 } | |
144 } | |
145 } | |
146 } | |
147 | |
148 /** | |
149 * Adds to [result] either [c] or the first ASCII character after it. | |
150 */ | |
151 void _addSingleCharacterName(Set<String> excluded, Set<String> result, int c) { | |
152 while (c < 0x7A) { | |
153 String name = new String.fromCharCode(c); | |
154 // may be done | |
155 if (!excluded.contains(name)) { | |
156 result.add(name); | |
157 break; | |
158 } | |
159 // next character | |
160 c = c + 1; | |
161 } | |
162 } | |
163 | |
164 String _getBaseNameFromExpression(Expression expression) { | |
165 String name = null; | |
166 // e as Type | |
167 if (expression is AsExpression) { | |
168 AsExpression asExpression = expression as AsExpression; | |
169 expression = asExpression.expression; | |
170 } | |
171 // analyze expressions | |
172 if (expression is SimpleIdentifier) { | |
173 SimpleIdentifier node = expression; | |
174 return node.name; | |
175 } else if (expression is PrefixedIdentifier) { | |
176 PrefixedIdentifier node = expression; | |
177 return node.identifier.name; | |
178 } else if (expression is MethodInvocation) { | |
179 name = expression.methodName.name; | |
180 } else if (expression is InstanceCreationExpression) { | |
181 InstanceCreationExpression creation = expression; | |
182 ConstructorName constructorName = creation.constructorName; | |
183 TypeName typeName = constructorName.type; | |
184 if (typeName != null) { | |
185 Identifier typeNameIdentifier = typeName.name; | |
186 // new ClassName() | |
187 if (typeNameIdentifier is SimpleIdentifier) { | |
188 return typeNameIdentifier.name; | |
189 } | |
190 // new prefix.name(); | |
191 if (typeNameIdentifier is PrefixedIdentifier) { | |
192 PrefixedIdentifier prefixed = typeNameIdentifier; | |
193 // new prefix.ClassName() | |
194 if (prefixed.prefix.staticElement is PrefixElement) { | |
195 return prefixed.identifier.name; | |
196 } | |
197 // new ClassName.constructorName() | |
198 return prefixed.prefix.name; | |
199 } | |
200 } | |
201 } | |
202 // strip known prefixes | |
203 if (name != null) { | |
204 for (int i = 0; i < _KNOWN_METHOD_NAME_PREFIXES.length; i++) { | |
205 String curr = _KNOWN_METHOD_NAME_PREFIXES[i]; | |
206 if (name.startsWith(curr)) { | |
207 if (name == curr) { | |
208 return null; | |
209 } else if (isUpperCase(name.codeUnitAt(curr.length))) { | |
210 return name.substring(curr.length); | |
211 } | |
212 } | |
213 } | |
214 } | |
215 // done | |
216 return name; | |
217 } | |
218 | |
219 | |
220 String _getBaseNameFromLocationInParent(Expression expression) { | |
221 // value in named expression | |
222 if (expression.parent is NamedExpression) { | |
223 NamedExpression namedExpression = expression.parent as NamedExpression; | |
224 if (namedExpression.expression == expression) { | |
225 return namedExpression.name.label.name; | |
226 } | |
227 } | |
228 // positional argument | |
229 { | |
230 ParameterElement parameter = expression.propagatedParameterElement; | |
231 if (parameter == null) { | |
232 parameter = expression.staticParameterElement; | |
233 } | |
234 if (parameter != null) { | |
235 return parameter.displayName; | |
236 } | |
237 } | |
238 // unknown | |
239 return null; | |
240 } | |
241 | |
242 /** | |
243 * Returns all variants of names by removing leading words one by one. | |
244 */ | |
245 List<String> _getCamelWordCombinations(String name) { | |
246 List<String> result = []; | |
247 List<String> parts = getCamelWords(name); | |
248 for (int i = 0; i < parts.length; i++) { | |
249 var s1 = parts[i].toLowerCase(); | |
250 var s2 = parts.skip(i + 1).join(); | |
251 String suggestion = '$s1$s2'; | |
252 result.add(suggestion); | |
253 } | |
254 return result; | |
255 } | |
OLD | NEW |