OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart._interceptors; | 5 part of dart._interceptors; |
6 | 6 |
7 /** | 7 /** |
8 * The interceptor class for [String]. The compiler recognizes this | 8 * The interceptor class for [String]. The compiler recognizes this |
9 * class as an interceptor, and changes references to [:this:] to | 9 * class as an interceptor, and changes references to [:this:] to |
10 * actually use the receiver of the method, which is generated as an extra | 10 * actually use the receiver of the method, which is generated as an extra |
11 * argument added to each member. | 11 * argument added to each member. |
12 */ | 12 */ |
13 @JsPeerInterface(name: 'String') | 13 @JsPeerInterface(name: 'String') |
14 class JSString extends Interceptor implements String, JSIndexable<String> { | 14 class JSString extends Interceptor implements String, JSIndexable<String> { |
15 const JSString(); | 15 const JSString(); |
16 | 16 |
17 int codeUnitAt(int index) { | 17 @notNull |
| 18 int codeUnitAt(@nullCheck int index) { |
18 // Suppress 2nd null check on index and null check on length | 19 // Suppress 2nd null check on index and null check on length |
19 // (JS String.length cannot be null). | 20 // (JS String.length cannot be null). |
20 if (index == null || | 21 final len = this.length; |
21 JS('int', '#', index) < 0 || | 22 if (index < 0 || index >= len) { |
22 JS('int', '#', index) >= JS('int', '#.length', this)) { | 23 throw new RangeError.index(index, this, 'index', null, len); |
23 throw diagnoseIndexError(this, index); | |
24 } | 24 } |
25 return JS('int', r'#.charCodeAt(#)', this, index); | 25 return JS('int', r'#.charCodeAt(#)', this, index); |
26 } | 26 } |
27 | 27 |
28 Iterable<Match> allMatches(String string, [int start = 0]) { | 28 @notNull |
29 checkString(string); | 29 Iterable<Match> allMatches(@nullCheck String string, |
30 checkInt(start); | 30 [@nullCheck int start = 0]) { |
31 if (0 > start || start > string.length) { | 31 final len = string.length; |
32 throw new RangeError.range(start, 0, string.length); | 32 if (0 > start || start > len) { |
| 33 throw new RangeError.range(start, 0, len); |
33 } | 34 } |
34 return allMatchesInStringUnchecked(this, string, start); | 35 return allMatchesInStringUnchecked(this, string, start); |
35 } | 36 } |
36 | 37 |
37 Match matchAsPrefix(String string, [int start = 0]) { | 38 Match matchAsPrefix(@nullCheck String string, [@nullCheck int start = 0]) { |
38 if (start < 0 || start > string.length) { | 39 final stringLength = JS('int', '#.length', string); |
39 throw new RangeError.range(start, 0, string.length); | 40 if (start < 0 || start > stringLength) { |
| 41 throw new RangeError.range(start, 0, stringLength); |
40 } | 42 } |
41 if (start + this.length > string.length) return null; | 43 final thisLength = JS('int', '#.length', this); |
42 // TODO(lrn): See if this can be optimized. | 44 if (start + thisLength > stringLength) return null; |
43 for (int i = 0; i < this.length; i++) { | 45 for (int i = 0; i < thisLength; i++) { |
44 if (string.codeUnitAt(start + i) != this.codeUnitAt(i)) { | 46 if (string.codeUnitAt(start + i) != this.codeUnitAt(i)) { |
45 return null; | 47 return null; |
46 } | 48 } |
47 } | 49 } |
48 return new StringMatch(start, string, this); | 50 return new StringMatch(start, string, this); |
49 } | 51 } |
50 | 52 |
51 String operator +(String other) { | 53 @notNull |
52 if (other is! String) throw new ArgumentError.value(other); | 54 String operator +(@nullCheck String other) { |
53 return JS('String', r'# + #', this, other); | 55 return JS('String', r'# + #', this, other); |
54 } | 56 } |
55 | 57 |
56 bool endsWith(String other) { | 58 @notNull |
57 checkString(other); | 59 bool endsWith(@nullCheck String other) { |
58 int otherLength = other.length; | 60 var otherLength = other.length; |
59 if (otherLength > length) return false; | 61 var thisLength = this.length; |
60 return other == substring(length - otherLength); | 62 if (otherLength > thisLength) return false; |
| 63 return other == substring(thisLength - otherLength); |
61 } | 64 } |
62 | 65 |
63 String replaceAll(Pattern from, String to) { | 66 @notNull |
64 checkString(to); | 67 String replaceAll(Pattern from, @nullCheck String to) { |
65 return stringReplaceAllUnchecked(this, from, to); | 68 return stringReplaceAllUnchecked(this, from, to); |
66 } | 69 } |
67 | 70 |
| 71 @notNull |
68 String replaceAllMapped(Pattern from, String convert(Match match)) { | 72 String replaceAllMapped(Pattern from, String convert(Match match)) { |
69 return this.splitMapJoin(from, onMatch: convert); | 73 return this.splitMapJoin(from, onMatch: convert); |
70 } | 74 } |
71 | 75 |
| 76 @notNull |
72 String splitMapJoin(Pattern from, | 77 String splitMapJoin(Pattern from, |
73 {String onMatch(Match match), String onNonMatch(String nonMatch)}) { | 78 {String onMatch(Match match), String onNonMatch(String nonMatch)}) { |
74 return stringReplaceAllFuncUnchecked(this, from, onMatch, onNonMatch); | 79 return stringReplaceAllFuncUnchecked(this, from, onMatch, onNonMatch); |
75 } | 80 } |
76 | 81 |
77 String replaceFirst(Pattern from, String to, [int startIndex = 0]) { | 82 @notNull |
78 checkString(to); | 83 String replaceFirst(Pattern from, @nullCheck String to, |
79 checkInt(startIndex); | 84 [@nullCheck int startIndex = 0]) { |
80 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex"); | 85 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex"); |
81 return stringReplaceFirstUnchecked(this, from, to, startIndex); | 86 return stringReplaceFirstUnchecked(this, from, to, startIndex); |
82 } | 87 } |
83 | 88 |
84 String replaceFirstMapped(Pattern from, String replace(Match match), | 89 @notNull |
85 [int startIndex = 0]) { | 90 String replaceFirstMapped( |
86 checkNull(replace); | 91 Pattern from, @nullCheck String replace(Match match), |
87 checkInt(startIndex); | 92 [@nullCheck int startIndex = 0]) { |
88 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex"); | 93 RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex"); |
89 return stringReplaceFirstMappedUnchecked(this, from, replace, startIndex); | 94 return stringReplaceFirstMappedUnchecked(this, from, replace, startIndex); |
90 } | 95 } |
91 | 96 |
92 List<String> split(Pattern pattern) { | 97 @notNull |
93 checkNull(pattern); | 98 List<String> split(@nullCheck Pattern pattern) { |
94 if (pattern is String) { | 99 if (pattern is String) { |
95 return JS('JSExtendableArray', r'#.split(#)', this, pattern); | 100 return JS('JSExtendableArray', r'#.split(#)', this, pattern); |
96 } else if (pattern is JSSyntaxRegExp && regExpCaptureCount(pattern) == 0) { | 101 } else if (pattern is JSSyntaxRegExp && regExpCaptureCount(pattern) == 0) { |
97 var re = regExpGetNative(pattern); | 102 var re = regExpGetNative(pattern); |
98 return JS('JSExtendableArray', r'#.split(#)', this, re); | 103 return JS('JSExtendableArray', r'#.split(#)', this, re); |
99 } else { | 104 } else { |
100 return _defaultSplit(pattern); | 105 return _defaultSplit(pattern); |
101 } | 106 } |
102 } | 107 } |
103 | 108 |
104 String replaceRange(int start, int end, String replacement) { | 109 @notNull |
105 checkString(replacement); | 110 String replaceRange( |
106 checkInt(start); | 111 @nullCheck int start, int end, @nullCheck String replacement) { |
107 end = RangeError.checkValidRange(start, end, this.length); | 112 end = RangeError.checkValidRange(start, end, this.length); |
108 checkInt(end); | |
109 return stringReplaceRangeUnchecked(this, start, end, replacement); | 113 return stringReplaceRangeUnchecked(this, start, end, replacement); |
110 } | 114 } |
111 | 115 |
| 116 @notNull |
112 List<String> _defaultSplit(Pattern pattern) { | 117 List<String> _defaultSplit(Pattern pattern) { |
113 List<String> result = <String>[]; | 118 List<String> result = <String>[]; |
114 // End of most recent match. That is, start of next part to add to result. | 119 // End of most recent match. That is, start of next part to add to result. |
115 int start = 0; | 120 int start = 0; |
116 // Length of most recent match. | 121 // Length of most recent match. |
117 // Set >0, so no match on the empty string causes the result to be [""]. | 122 // Set >0, so no match on the empty string causes the result to be [""]. |
118 int length = 1; | 123 int length = 1; |
119 for (var match in pattern.allMatches(this)) { | 124 for (var match in pattern.allMatches(this)) { |
| 125 @notNull |
120 int matchStart = match.start; | 126 int matchStart = match.start; |
| 127 @notNull |
121 int matchEnd = match.end; | 128 int matchEnd = match.end; |
122 length = matchEnd - matchStart; | 129 length = matchEnd - matchStart; |
123 if (length == 0 && start == matchStart) { | 130 if (length == 0 && start == matchStart) { |
124 // An empty match right after another match is ignored. | 131 // An empty match right after another match is ignored. |
125 // This includes an empty match at the start of the string. | 132 // This includes an empty match at the start of the string. |
126 continue; | 133 continue; |
127 } | 134 } |
128 int end = matchStart; | 135 int end = matchStart; |
129 result.add(this.substring(start, end)); | 136 result.add(this.substring(start, end)); |
130 start = matchEnd; | 137 start = matchEnd; |
131 } | 138 } |
132 if (start < this.length || length > 0) { | 139 if (start < this.length || length > 0) { |
133 // An empty match at the end of the string does not cause a "" at the end. | 140 // An empty match at the end of the string does not cause a "" at the end. |
134 // A non-empty match ending at the end of the string does add a "". | 141 // A non-empty match ending at the end of the string does add a "". |
135 result.add(this.substring(start)); | 142 result.add(this.substring(start)); |
136 } | 143 } |
137 return result; | 144 return result; |
138 } | 145 } |
139 | 146 |
140 bool startsWith(Pattern pattern, [int index = 0]) { | 147 @notNull |
| 148 bool startsWith(Pattern pattern, [@nullCheck int index = 0]) { |
141 // Suppress null check on length and all but the first | 149 // Suppress null check on length and all but the first |
142 // reference to index. | 150 // reference to index. |
143 int length = JS('int', '#.length', this); | 151 int length = JS('int', '#.length', this); |
144 if (index < 0 || JS('int', '#', index) > length) { | 152 if (index < 0 || JS('int', '#', index) > length) { |
145 throw new RangeError.range(index, 0, this.length); | 153 throw new RangeError.range(index, 0, this.length); |
146 } | 154 } |
147 if (pattern is String) { | 155 if (pattern is String) { |
148 String other = pattern; | 156 String other = pattern; |
149 int otherLength = JS('int', '#.length', other); | 157 int otherLength = JS('int', '#.length', other); |
150 int endIndex = JS('int', '#', index) + otherLength; | 158 int endIndex = index + otherLength; |
151 if (endIndex > length) return false; | 159 if (endIndex > length) return false; |
152 return other == JS('String', r'#.substring(#, #)', this, index, endIndex); | 160 return other == JS('String', r'#.substring(#, #)', this, index, endIndex); |
153 } | 161 } |
154 return pattern.matchAsPrefix(this, index) != null; | 162 return pattern.matchAsPrefix(this, index) != null; |
155 } | 163 } |
156 | 164 |
157 String substring(int startIndex, [int endIndex]) { | 165 @notNull |
158 checkInt(startIndex); | 166 String substring(@nullCheck int startIndex, [int _endIndex]) { |
159 if (endIndex == null) endIndex = length; | 167 var length = this.length; |
160 checkInt(endIndex); | 168 final endIndex = _endIndex ?? length; |
161 if (startIndex < 0) throw new RangeError.value(startIndex); | 169 if (startIndex < 0) throw new RangeError.value(startIndex); |
162 if (startIndex > endIndex) throw new RangeError.value(startIndex); | 170 if (startIndex > endIndex) throw new RangeError.value(startIndex); |
163 if (endIndex > length) throw new RangeError.value(endIndex); | 171 if (endIndex > length) throw new RangeError.value(endIndex); |
164 return JS('String', r'#.substring(#, #)', this, startIndex, endIndex); | 172 return JS('String', r'#.substring(#, #)', this, startIndex, endIndex); |
165 } | 173 } |
166 | 174 |
| 175 @notNull |
167 String toLowerCase() { | 176 String toLowerCase() { |
168 return JS('String', r'#.toLowerCase()', this); | 177 return JS('String', r'#.toLowerCase()', this); |
169 } | 178 } |
170 | 179 |
| 180 @notNull |
171 String toUpperCase() { | 181 String toUpperCase() { |
172 return JS('String', r'#.toUpperCase()', this); | 182 return JS('String', r'#.toUpperCase()', this); |
173 } | 183 } |
174 | 184 |
175 // Characters with Whitespace property (Unicode 6.2). | 185 // Characters with Whitespace property (Unicode 6.2). |
176 // 0009..000D ; White_Space # Cc <control-0009>..<control-000D> | 186 // 0009..000D ; White_Space # Cc <control-0009>..<control-000D> |
177 // 0020 ; White_Space # Zs SPACE | 187 // 0020 ; White_Space # Zs SPACE |
178 // 0085 ; White_Space # Cc <control-0085> | 188 // 0085 ; White_Space # Cc <control-0085> |
179 // 00A0 ; White_Space # Zs NO-BREAK SPACE | 189 // 00A0 ; White_Space # Zs NO-BREAK SPACE |
180 // 1680 ; White_Space # Zs OGHAM SPACE MARK | 190 // 1680 ; White_Space # Zs OGHAM SPACE MARK |
181 // 180E ; White_Space # Zs MONGOLIAN VOWEL SEPARATOR | 191 // 180E ; White_Space # Zs MONGOLIAN VOWEL SEPARATOR |
182 // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE | 192 // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE |
183 // 2028 ; White_Space # Zl LINE SEPARATOR | 193 // 2028 ; White_Space # Zl LINE SEPARATOR |
184 // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR | 194 // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR |
185 // 202F ; White_Space # Zs NARROW NO-BREAK SPACE | 195 // 202F ; White_Space # Zs NARROW NO-BREAK SPACE |
186 // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE | 196 // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE |
187 // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE | 197 // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE |
188 // | 198 // |
189 // BOM: 0xFEFF | 199 // BOM: 0xFEFF |
190 static bool _isWhitespace(int codeUnit) { | 200 @notNull |
| 201 static bool _isWhitespace(@notNull int codeUnit) { |
191 // Most codeUnits should be less than 256. Special case with a smaller | 202 // Most codeUnits should be less than 256. Special case with a smaller |
192 // switch. | 203 // switch. |
193 if (codeUnit < 256) { | 204 if (codeUnit < 256) { |
194 switch (codeUnit) { | 205 switch (codeUnit) { |
195 case 0x09: | 206 case 0x09: |
196 case 0x0A: | 207 case 0x0A: |
197 case 0x0B: | 208 case 0x0B: |
198 case 0x0C: | 209 case 0x0C: |
199 case 0x0D: | 210 case 0x0D: |
200 case 0x20: | 211 case 0x20: |
(...skipping 25 matching lines...) Expand all Loading... |
226 case 0x3000: | 237 case 0x3000: |
227 case 0xFEFF: | 238 case 0xFEFF: |
228 return true; | 239 return true; |
229 default: | 240 default: |
230 return false; | 241 return false; |
231 } | 242 } |
232 } | 243 } |
233 | 244 |
234 /// Finds the index of the first non-whitespace character, or the | 245 /// Finds the index of the first non-whitespace character, or the |
235 /// end of the string. Start looking at position [index]. | 246 /// end of the string. Start looking at position [index]. |
236 static int _skipLeadingWhitespace(String string, int index) { | 247 @notNull |
| 248 static int _skipLeadingWhitespace(String string, @nullCheck int index) { |
237 const int SPACE = 0x20; | 249 const int SPACE = 0x20; |
238 const int CARRIAGE_RETURN = 0x0D; | 250 const int CARRIAGE_RETURN = 0x0D; |
239 while (index < string.length) { | 251 var stringLength = string.length; |
| 252 while (index < stringLength) { |
240 int codeUnit = string.codeUnitAt(index); | 253 int codeUnit = string.codeUnitAt(index); |
241 if (codeUnit != SPACE && | 254 if (codeUnit != SPACE && |
242 codeUnit != CARRIAGE_RETURN && | 255 codeUnit != CARRIAGE_RETURN && |
243 !_isWhitespace(codeUnit)) { | 256 !_isWhitespace(codeUnit)) { |
244 break; | 257 break; |
245 } | 258 } |
246 index++; | 259 index++; |
247 } | 260 } |
248 return index; | 261 return index; |
249 } | 262 } |
250 | 263 |
251 /// Finds the index after the last non-whitespace character, or 0. | 264 /// Finds the index after the last non-whitespace character, or 0. |
252 /// Start looking at position [index - 1]. | 265 /// Start looking at position [index - 1]. |
253 static int _skipTrailingWhitespace(String string, int index) { | 266 @notNull |
| 267 static int _skipTrailingWhitespace(String string, @nullCheck int index) { |
254 const int SPACE = 0x20; | 268 const int SPACE = 0x20; |
255 const int CARRIAGE_RETURN = 0x0D; | 269 const int CARRIAGE_RETURN = 0x0D; |
256 while (index > 0) { | 270 while (index > 0) { |
257 int codeUnit = string.codeUnitAt(index - 1); | 271 int codeUnit = string.codeUnitAt(index - 1); |
258 if (codeUnit != SPACE && | 272 if (codeUnit != SPACE && |
259 codeUnit != CARRIAGE_RETURN && | 273 codeUnit != CARRIAGE_RETURN && |
260 !_isWhitespace(codeUnit)) { | 274 !_isWhitespace(codeUnit)) { |
261 break; | 275 break; |
262 } | 276 } |
263 index--; | 277 index--; |
264 } | 278 } |
265 return index; | 279 return index; |
266 } | 280 } |
267 | 281 |
268 // Dart2js can't use JavaScript trim directly, | 282 // Dart2js can't use JavaScript trim directly, |
269 // because JavaScript does not trim | 283 // because JavaScript does not trim |
270 // the NEXT LINE (NEL) character (0x85). | 284 // the NEXT LINE (NEL) character (0x85). |
| 285 @notNull |
271 String trim() { | 286 String trim() { |
272 const int NEL = 0x85; | 287 const int NEL = 0x85; |
273 | 288 |
274 // Start by doing JS trim. Then check if it leaves a NEL at | 289 // Start by doing JS trim. Then check if it leaves a NEL at |
275 // either end of the string. | 290 // either end of the string. |
276 String result = JS('String', '#.trim()', this); | 291 final result = JS('String', '#.trim()', this); |
277 if (result.length == 0) return result; | 292 final length = result.length; |
| 293 if (length == 0) return result; |
278 int firstCode = result.codeUnitAt(0); | 294 int firstCode = result.codeUnitAt(0); |
279 int startIndex = 0; | 295 int startIndex = 0; |
280 if (firstCode == NEL) { | 296 if (firstCode == NEL) { |
281 startIndex = _skipLeadingWhitespace(result, 1); | 297 startIndex = _skipLeadingWhitespace(result, 1); |
282 if (startIndex == result.length) return ""; | 298 if (startIndex == length) return ""; |
283 } | 299 } |
284 | 300 |
285 int endIndex = result.length; | 301 int endIndex = length; |
286 // We know that there is at least one character that is non-whitespace. | 302 // We know that there is at least one character that is non-whitespace. |
287 // Therefore we don't need to verify that endIndex > startIndex. | 303 // Therefore we don't need to verify that endIndex > startIndex. |
288 int lastCode = result.codeUnitAt(endIndex - 1); | 304 int lastCode = result.codeUnitAt(endIndex - 1); |
289 if (lastCode == NEL) { | 305 if (lastCode == NEL) { |
290 endIndex = _skipTrailingWhitespace(result, endIndex - 1); | 306 endIndex = _skipTrailingWhitespace(result, endIndex - 1); |
291 } | 307 } |
292 if (startIndex == 0 && endIndex == result.length) return result; | 308 if (startIndex == 0 && endIndex == length) return result; |
293 return JS('String', r'#.substring(#, #)', result, startIndex, endIndex); | 309 return JS('String', r'#.substring(#, #)', result, startIndex, endIndex); |
294 } | 310 } |
295 | 311 |
296 // Dart2js can't use JavaScript trimLeft directly, | 312 // Dart2js can't use JavaScript trimLeft directly, |
297 // because it is not in ES5, so not every browser implements it, | 313 // because it is not in ES5, so not every browser implements it, |
298 // and because those that do will not trim the NEXT LINE character (0x85). | 314 // and because those that do will not trim the NEXT LINE character (0x85). |
| 315 @notNull |
299 String trimLeft() { | 316 String trimLeft() { |
300 const int NEL = 0x85; | 317 const int NEL = 0x85; |
301 | 318 |
302 // Start by doing JS trim. Then check if it leaves a NEL at | 319 // Start by doing JS trim. Then check if it leaves a NEL at |
303 // the beginning of the string. | 320 // the beginning of the string. |
304 String result; | 321 String result; |
305 int startIndex = 0; | 322 int startIndex = 0; |
306 if (JS('bool', 'typeof #.trimLeft != "undefined"', this)) { | 323 if (JS('bool', 'typeof #.trimLeft != "undefined"', this)) { |
307 result = JS('String', '#.trimLeft()', this); | 324 result = JS('String', '#.trimLeft()', this); |
308 if (result.length == 0) return result; | 325 if (result.length == 0) return result; |
309 int firstCode = result.codeUnitAt(0); | 326 int firstCode = result.codeUnitAt(0); |
310 if (firstCode == NEL) { | 327 if (firstCode == NEL) { |
311 startIndex = _skipLeadingWhitespace(result, 1); | 328 startIndex = _skipLeadingWhitespace(result, 1); |
312 } | 329 } |
313 } else { | 330 } else { |
314 result = this; | 331 result = this; |
315 startIndex = _skipLeadingWhitespace(this, 0); | 332 startIndex = _skipLeadingWhitespace(this, 0); |
316 } | 333 } |
317 if (startIndex == 0) return result; | 334 if (startIndex == 0) return result; |
318 if (startIndex == result.length) return ""; | 335 if (startIndex == result.length) return ""; |
319 return JS('String', r'#.substring(#)', result, startIndex); | 336 return JS('String', r'#.substring(#)', result, startIndex); |
320 } | 337 } |
321 | 338 |
322 // Dart2js can't use JavaScript trimRight directly, | 339 // Dart2js can't use JavaScript trimRight directly, |
323 // because it is not in ES5 and because JavaScript does not trim | 340 // because it is not in ES5 and because JavaScript does not trim |
324 // the NEXT LINE character (0x85). | 341 // the NEXT LINE character (0x85). |
| 342 @notNull |
325 String trimRight() { | 343 String trimRight() { |
326 const int NEL = 0x85; | 344 const int NEL = 0x85; |
327 | 345 |
328 // Start by doing JS trim. Then check if it leaves a NEL or BOM at | 346 // Start by doing JS trim. Then check if it leaves a NEL or BOM at |
329 // the end of the string. | 347 // the end of the string. |
330 String result; | 348 String result; |
331 int endIndex; | 349 @notNull |
| 350 int endIndex = 0; |
332 // trimRight is implemented by Firefox and Chrome/Blink, | 351 // trimRight is implemented by Firefox and Chrome/Blink, |
333 // so use it if it is there. | 352 // so use it if it is there. |
334 if (JS('bool', 'typeof #.trimRight != "undefined"', this)) { | 353 if (JS('bool', 'typeof #.trimRight != "undefined"', this)) { |
335 result = JS('String', '#.trimRight()', this); | 354 result = JS('String', '#.trimRight()', this); |
336 endIndex = result.length; | 355 endIndex = result.length; |
337 if (endIndex == 0) return result; | 356 if (endIndex == 0) return result; |
338 int lastCode = result.codeUnitAt(endIndex - 1); | 357 int lastCode = result.codeUnitAt(endIndex - 1); |
339 if (lastCode == NEL) { | 358 if (lastCode == NEL) { |
340 endIndex = _skipTrailingWhitespace(result, endIndex - 1); | 359 endIndex = _skipTrailingWhitespace(result, endIndex - 1); |
341 } | 360 } |
342 } else { | 361 } else { |
343 result = this; | 362 result = this; |
344 endIndex = _skipTrailingWhitespace(this, this.length); | 363 endIndex = _skipTrailingWhitespace(this, this.length); |
345 } | 364 } |
346 | 365 |
347 if (endIndex == result.length) return result; | 366 if (endIndex == result.length) return result; |
348 if (endIndex == 0) return ""; | 367 if (endIndex == 0) return ""; |
349 return JS('String', r'#.substring(#, #)', result, 0, endIndex); | 368 return JS('String', r'#.substring(#, #)', result, 0, endIndex); |
350 } | 369 } |
351 | 370 |
352 String operator *(int times) { | 371 @notNull |
353 if (0 >= times) return ''; // Unnecessary but hoists argument type check. | 372 String operator *(@nullCheck int times) { |
| 373 if (0 >= times) return ''; |
354 if (times == 1 || this.length == 0) return this; | 374 if (times == 1 || this.length == 0) return this; |
355 if (times != JS('int', '# >>> 0', times)) { | 375 if (times != JS('int', '# >>> 0', times)) { |
356 // times >= 2^32. We can't create a string that big. | 376 // times >= 2^32. We can't create a string that big. |
357 throw const OutOfMemoryError(); | 377 throw const OutOfMemoryError(); |
358 } | 378 } |
359 var result = ''; | 379 var result = ''; |
360 String s = this; | 380 String s = this; |
361 while (true) { | 381 while (true) { |
362 if (times & 1 == 1) result = s + result; | 382 if (times & 1 == 1) result = s + result; |
363 times = JS('int', '# >>> 1', times); | 383 times = JS('int', '# >>> 1', times); |
364 if (times == 0) break; | 384 if (times == 0) break; |
365 s += s; | 385 s += s; |
366 } | 386 } |
367 return result; | 387 return result; |
368 } | 388 } |
369 | 389 |
370 String padLeft(int width, [String padding = ' ']) { | 390 @notNull |
| 391 String padLeft(@nullCheck int width, [String padding = ' ']) { |
371 int delta = width - this.length; | 392 int delta = width - this.length; |
372 if (delta <= 0) return this; | 393 if (delta <= 0) return this; |
373 return padding * delta + this; | 394 return padding * delta + this; |
374 } | 395 } |
375 | 396 |
376 String padRight(int width, [String padding = ' ']) { | 397 @notNull |
| 398 String padRight(@nullCheck int width, [String padding = ' ']) { |
377 int delta = width - this.length; | 399 int delta = width - this.length; |
378 if (delta <= 0) return this; | 400 if (delta <= 0) return this; |
379 return this + padding * delta; | 401 return this + padding * delta; |
380 } | 402 } |
381 | 403 |
| 404 @notNull |
382 List<int> get codeUnits => new CodeUnits(this); | 405 List<int> get codeUnits => new CodeUnits(this); |
383 | 406 |
| 407 @notNull |
384 Runes get runes => new Runes(this); | 408 Runes get runes => new Runes(this); |
385 | 409 |
386 int indexOf(Pattern pattern, [int start = 0]) { | 410 @notNull |
387 checkNull(pattern); | 411 int indexOf(@nullCheck Pattern pattern, [@nullCheck int start = 0]) { |
388 if (start is! int) throw argumentErrorValue(start); | |
389 if (start < 0 || start > this.length) { | 412 if (start < 0 || start > this.length) { |
390 throw new RangeError.range(start, 0, this.length); | 413 throw new RangeError.range(start, 0, this.length); |
391 } | 414 } |
392 if (pattern is String) { | 415 if (pattern is String) { |
393 return stringIndexOfStringUnchecked(this, pattern, start); | 416 return stringIndexOfStringUnchecked(this, pattern, start); |
394 } | 417 } |
395 if (pattern is JSSyntaxRegExp) { | 418 if (pattern is JSSyntaxRegExp) { |
396 JSSyntaxRegExp re = pattern; | 419 JSSyntaxRegExp re = pattern; |
397 Match match = firstMatchAfter(re, this, start); | 420 Match match = firstMatchAfter(re, this, start); |
398 return (match == null) ? -1 : match.start; | 421 return (match == null) ? -1 : match.start; |
399 } | 422 } |
400 for (int i = start; i <= this.length; i++) { | 423 var length = this.length; |
| 424 for (int i = start; i <= length; i++) { |
401 if (pattern.matchAsPrefix(this, i) != null) return i; | 425 if (pattern.matchAsPrefix(this, i) != null) return i; |
402 } | 426 } |
403 return -1; | 427 return -1; |
404 } | 428 } |
405 | 429 |
406 int lastIndexOf(Pattern pattern, [int start]) { | 430 @notNull |
407 checkNull(pattern); | 431 int lastIndexOf(@nullCheck Pattern pattern, [int _start]) { |
408 if (start == null) { | 432 var length = this.length; |
409 start = length; | 433 var start = _start ?? length; |
410 } else if (start is! int) { | 434 if (start < 0 || start > length) { |
411 throw argumentErrorValue(start); | 435 throw new RangeError.range(start, 0, length); |
412 } else if (start < 0 || start > this.length) { | |
413 throw new RangeError.range(start, 0, this.length); | |
414 } | 436 } |
415 if (pattern is String) { | 437 if (pattern is String) { |
416 String other = pattern; | 438 String other = pattern; |
417 if (start + other.length > this.length) { | 439 if (start + other.length > length) { |
418 start = this.length - other.length; | 440 start = length - other.length; |
419 } | 441 } |
420 return stringLastIndexOfUnchecked(this, other, start); | 442 return stringLastIndexOfUnchecked(this, other, start); |
421 } | 443 } |
422 for (int i = start; i >= 0; i--) { | 444 for (int i = start; i >= 0; i--) { |
423 if (pattern.matchAsPrefix(this, i) != null) return i; | 445 if (pattern.matchAsPrefix(this, i) != null) return i; |
424 } | 446 } |
425 return -1; | 447 return -1; |
426 } | 448 } |
427 | 449 |
428 bool contains(Pattern other, [int startIndex = 0]) { | 450 @notNull |
429 checkNull(other); | 451 bool contains(@nullCheck Pattern other, [@nullCheck int startIndex = 0]) { |
430 if (startIndex < 0 || startIndex > this.length) { | 452 if (startIndex < 0 || startIndex > this.length) { |
431 throw new RangeError.range(startIndex, 0, this.length); | 453 throw new RangeError.range(startIndex, 0, this.length); |
432 } | 454 } |
433 return stringContainsUnchecked(this, other, startIndex); | 455 return stringContainsUnchecked(this, other, startIndex); |
434 } | 456 } |
435 | 457 |
| 458 @notNull |
436 bool get isEmpty => JS('int', '#.length', this) == 0; | 459 bool get isEmpty => JS('int', '#.length', this) == 0; |
437 | 460 |
| 461 @notNull |
438 bool get isNotEmpty => !isEmpty; | 462 bool get isNotEmpty => !isEmpty; |
439 | 463 |
440 int compareTo(String other) { | 464 @notNull |
441 if (other == null) throw argumentErrorValue(other); | 465 int compareTo(@nullCheck String other) { |
442 return this == other ? 0 : JS('bool', r'# < #', this, other) ? -1 : 1; | 466 return this == other ? 0 : JS('bool', r'# < #', this, other) ? -1 : 1; |
443 } | 467 } |
444 | 468 |
445 // Note: if you change this, also change the function [S]. | 469 // Note: if you change this, also change the function [S]. |
| 470 @notNull |
446 String toString() => this; | 471 String toString() => this; |
447 | 472 |
448 /** | 473 /** |
449 * This is the [Jenkins hash function][1] but using masking to keep | 474 * This is the [Jenkins hash function][1] but using masking to keep |
450 * values in SMI range. | 475 * values in SMI range. |
451 * | 476 * |
452 * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function | 477 * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function |
453 */ | 478 */ |
| 479 @notNull |
454 int get hashCode { | 480 int get hashCode { |
455 // TODO(ahe): This method shouldn't have to use JS. Update when our | 481 // TODO(ahe): This method shouldn't have to use JS. Update when our |
456 // optimizations are smarter. | 482 // optimizations are smarter. |
457 int hash = 0; | 483 int hash = 0; |
458 int length = JS('int', '#.length', this); | 484 int length = JS('int', '#.length', this); |
459 for (int i = 0; i < length; i++) { | 485 for (int i = 0; i < length; i++) { |
460 hash = 0x1fffffff & (hash + JS('int', r'#.charCodeAt(#)', this, i)); | 486 hash = 0x1fffffff & (hash + JS('int', r'#.charCodeAt(#)', this, i)); |
461 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | 487 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); |
462 hash = JS('int', '# ^ (# >> 6)', hash, hash); | 488 hash = JS('int', '# ^ (# >> 6)', hash, hash); |
463 } | 489 } |
464 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | 490 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); |
465 hash = JS('int', '# ^ (# >> 11)', hash, hash); | 491 hash = JS('int', '# ^ (# >> 11)', hash, hash); |
466 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | 492 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); |
467 } | 493 } |
468 | 494 |
| 495 @notNull |
469 Type get runtimeType => String; | 496 Type get runtimeType => String; |
470 | 497 |
| 498 @notNull |
471 int get length => JS('int', r'#.length', this); | 499 int get length => JS('int', r'#.length', this); |
472 | 500 |
473 String operator [](int index) { | 501 @notNull |
474 if (index == null || | 502 String operator [](@nullCheck int index) { |
475 JS('int', '#', index) >= JS('int', '#.length', this) || | 503 if (index >= JS('int', '#.length', this) || index < 0) { |
476 JS('int', '#', index) < 0) { | |
477 throw diagnoseIndexError(this, index); | 504 throw diagnoseIndexError(this, index); |
478 } | 505 } |
479 return JS('String', '#[#]', this, index); | 506 return JS('String', '#[#]', this, index); |
480 } | 507 } |
481 } | 508 } |
OLD | NEW |