| 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 |