| 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.util; | |
| 6 | |
| 7 import 'package:analysis_services/correction/change.dart'; | |
| 8 import 'package:analysis_services/src/correction/source_range.dart'; | |
| 9 import 'package:analysis_services/src/correction/strings.dart'; | |
| 10 import 'package:analyzer/src/generated/ast.dart'; | |
| 11 import 'package:analyzer/src/generated/element.dart'; | |
| 12 import 'package:analyzer/src/generated/engine.dart'; | |
| 13 import 'package:analyzer/src/generated/resolver.dart'; | |
| 14 import 'package:analyzer/src/generated/scanner.dart'; | |
| 15 import 'package:analyzer/src/generated/source.dart'; | |
| 16 | |
| 17 | |
| 18 /** | |
| 19 * TODO(scheglov) replace with nodes once there will be [CompilationUnit#getComm
ents]. | |
| 20 * | |
| 21 * Returns [SourceRange]s of all comments in [unit]. | |
| 22 */ | |
| 23 List<SourceRange> getCommentRanges(CompilationUnit unit) { | |
| 24 List<SourceRange> ranges = <SourceRange>[]; | |
| 25 Token token = unit.beginToken; | |
| 26 while (token != null && token.type != TokenType.EOF) { | |
| 27 Token commentToken = token.precedingComments; | |
| 28 while (commentToken != null) { | |
| 29 ranges.add(rangeToken(commentToken)); | |
| 30 commentToken = commentToken.next; | |
| 31 } | |
| 32 token = token.next; | |
| 33 } | |
| 34 return ranges; | |
| 35 } | |
| 36 | |
| 37 | |
| 38 String getDefaultValueCode(DartType type) { | |
| 39 if (type != null) { | |
| 40 String typeName = type.displayName; | |
| 41 if (typeName == "bool") { | |
| 42 return "false"; | |
| 43 } | |
| 44 if (typeName == "int") { | |
| 45 return "0"; | |
| 46 } | |
| 47 if (typeName == "double") { | |
| 48 return "0.0"; | |
| 49 } | |
| 50 if (typeName == "String") { | |
| 51 return "''"; | |
| 52 } | |
| 53 } | |
| 54 // no better guess | |
| 55 return "null"; | |
| 56 } | |
| 57 | |
| 58 | |
| 59 /** | |
| 60 * Return the name of the [Element] kind. | |
| 61 */ | |
| 62 String getElementKindName(Element element) { | |
| 63 return element.kind.displayName; | |
| 64 } | |
| 65 | |
| 66 | |
| 67 /** | |
| 68 * Returns the name to display in the UI for the given [Element]. | |
| 69 */ | |
| 70 String getElementQualifiedName(Element element) { | |
| 71 ElementKind kind = element.kind; | |
| 72 if (kind == ElementKind.FIELD || kind == ElementKind.METHOD) { | |
| 73 return '${element.enclosingElement.displayName}.${element.displayName}'; | |
| 74 } else { | |
| 75 return element.displayName; | |
| 76 } | |
| 77 } | |
| 78 | |
| 79 | |
| 80 /** | |
| 81 * @return the [ExecutableElement] of the enclosing executable [AstNode]. | |
| 82 */ | |
| 83 ExecutableElement getEnclosingExecutableElement(AstNode node) { | |
| 84 while (node != null) { | |
| 85 if (node is FunctionDeclaration) { | |
| 86 return node.element; | |
| 87 } | |
| 88 if (node is ConstructorDeclaration) { | |
| 89 return node.element; | |
| 90 } | |
| 91 if (node is MethodDeclaration) { | |
| 92 return node.element; | |
| 93 } | |
| 94 node = node.parent; | |
| 95 } | |
| 96 return null; | |
| 97 } | |
| 98 | |
| 99 /** | |
| 100 * Returns a namespace of the given [ExportElement]. | |
| 101 */ | |
| 102 Map<String, Element> getExportNamespaceForDirective(ExportElement exp) { | |
| 103 Namespace namespace = | |
| 104 new NamespaceBuilder().createExportNamespaceForDirective(exp); | |
| 105 return namespace.definedNames; | |
| 106 } | |
| 107 | |
| 108 | |
| 109 /** | |
| 110 * Returns a export namespace of the given [LibraryElement]. | |
| 111 */ | |
| 112 Map<String, Element> getExportNamespaceForLibrary(LibraryElement library) { | |
| 113 Namespace namespace = | |
| 114 new NamespaceBuilder().createExportNamespaceForLibrary(library); | |
| 115 return namespace.definedNames; | |
| 116 } | |
| 117 | |
| 118 /** | |
| 119 * Returns an [Element] exported from the given [LibraryElement]. | |
| 120 */ | |
| 121 Element getExportedElement(LibraryElement library, String name) { | |
| 122 if (library == null) { | |
| 123 return null; | |
| 124 } | |
| 125 return getExportNamespaceForLibrary(library)[name]; | |
| 126 } | |
| 127 | |
| 128 /** | |
| 129 * Returns [getExpressionPrecedence] for the parent of [node], | |
| 130 * or `0` if the parent node is [ParenthesizedExpression]. | |
| 131 * | |
| 132 * The reason is that `(expr)` is always executed after `expr`. | |
| 133 */ | |
| 134 int getExpressionParentPrecedence(AstNode node) { | |
| 135 AstNode parent = node.parent; | |
| 136 if (parent is ParenthesizedExpression) { | |
| 137 return 0; | |
| 138 } | |
| 139 return getExpressionPrecedence(parent); | |
| 140 } | |
| 141 | |
| 142 /** | |
| 143 * Returns the precedence of [node] it is an [Expression], negative otherwise. | |
| 144 */ | |
| 145 int getExpressionPrecedence(AstNode node) { | |
| 146 if (node is Expression) { | |
| 147 return node.precedence; | |
| 148 } | |
| 149 return -1000; | |
| 150 } | |
| 151 | |
| 152 /** | |
| 153 * Returns the namespace of the given [ImportElement]. | |
| 154 */ | |
| 155 Map<String, Element> getImportNamespace(ImportElement imp) { | |
| 156 NamespaceBuilder builder = new NamespaceBuilder(); | |
| 157 Namespace namespace = builder.createImportNamespaceForDirective(imp); | |
| 158 return namespace.definedNames; | |
| 159 } | |
| 160 | |
| 161 | |
| 162 /** | |
| 163 * If given [AstNode] is name of qualified property extraction, returns target f
rom which | |
| 164 * this property is extracted. Otherwise `null`. | |
| 165 */ | |
| 166 Expression getQualifiedPropertyTarget(AstNode node) { | |
| 167 AstNode parent = node.parent; | |
| 168 if (parent is PrefixedIdentifier) { | |
| 169 PrefixedIdentifier prefixed = parent; | |
| 170 if (prefixed.identifier == node) { | |
| 171 return parent.prefix; | |
| 172 } | |
| 173 } | |
| 174 if (parent is PropertyAccess) { | |
| 175 PropertyAccess access = parent; | |
| 176 if (access.propertyName == node) { | |
| 177 return access.realTarget; | |
| 178 } | |
| 179 } | |
| 180 return null; | |
| 181 } | |
| 182 | |
| 183 | |
| 184 /** | |
| 185 * Returns the given [Statement] if not a [Block], or the first child | |
| 186 * [Statement] if a [Block], or `null` if more than one child. | |
| 187 */ | |
| 188 Statement getSingleStatement(Statement statement) { | |
| 189 if (statement is Block) { | |
| 190 List<Statement> blockStatements = statement.statements; | |
| 191 if (blockStatements.length != 1) { | |
| 192 return null; | |
| 193 } | |
| 194 return blockStatements[0]; | |
| 195 } | |
| 196 return statement; | |
| 197 } | |
| 198 | |
| 199 | |
| 200 /** | |
| 201 * Returns the [String] content of the given [Source]. | |
| 202 */ | |
| 203 String getSourceContent(AnalysisContext context, Source source) { | |
| 204 return context.getContents(source).data; | |
| 205 } | |
| 206 | |
| 207 | |
| 208 /** | |
| 209 * Returns the given [Statement] if not a [Block], or all the children | |
| 210 * [Statement]s if a [Block]. | |
| 211 */ | |
| 212 List<Statement> getStatements(Statement statement) { | |
| 213 if (statement is Block) { | |
| 214 return statement.statements; | |
| 215 } | |
| 216 return [statement]; | |
| 217 } | |
| 218 | |
| 219 | |
| 220 /** | |
| 221 * Checks if the given [Element]'s display name equals to the given name. | |
| 222 */ | |
| 223 bool hasDisplayName(Element element, String name) { | |
| 224 if (element == null) { | |
| 225 return false; | |
| 226 } | |
| 227 return element.displayName == name; | |
| 228 } | |
| 229 | |
| 230 | |
| 231 class CorrectionUtils { | |
| 232 final CompilationUnit unit; | |
| 233 | |
| 234 LibraryElement _library; | |
| 235 String _buffer; | |
| 236 String _endOfLine; | |
| 237 | |
| 238 CorrectionUtils(this.unit) { | |
| 239 CompilationUnitElement unitElement = unit.element; | |
| 240 this._library = unitElement.library; | |
| 241 this._buffer = unitElement.context.getContents(unitElement.source).data; | |
| 242 } | |
| 243 | |
| 244 /** | |
| 245 * Returns the EOL to use for this [CompilationUnit]. | |
| 246 */ | |
| 247 String get endOfLine { | |
| 248 if (_endOfLine == null) { | |
| 249 if (_buffer.contains("\r\n")) { | |
| 250 _endOfLine = "\r\n"; | |
| 251 } else { | |
| 252 _endOfLine = "\n"; | |
| 253 } | |
| 254 } | |
| 255 return _endOfLine; | |
| 256 } | |
| 257 | |
| 258 /** | |
| 259 * Returns an [Edit] that changes indentation of the source of the given | |
| 260 * [SourceRange] from [oldIndent] to [newIndent], keeping indentation of lines | |
| 261 * relative to each other. | |
| 262 */ | |
| 263 Edit createIndentEdit(SourceRange range, String oldIndent, String newIndent) { | |
| 264 String newSource = replaceSourceRangeIndent(range, oldIndent, newIndent); | |
| 265 return new Edit(range.offset, range.length, newSource); | |
| 266 } | |
| 267 | |
| 268 /** | |
| 269 * Returns the actual type source of the given [Expression], may be `null` | |
| 270 * if can not be resolved, should be treated as the `dynamic` type. | |
| 271 */ | |
| 272 String getExpressionTypeSource(Expression expression) { | |
| 273 if (expression == null) { | |
| 274 return null; | |
| 275 } | |
| 276 DartType type = expression.bestType; | |
| 277 if (type.isDynamic) { | |
| 278 return null; | |
| 279 } | |
| 280 return getTypeSource(type); | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * Returns the indentation with the given level. | |
| 285 */ | |
| 286 String getIndent(int level) => repeat(' ', level); | |
| 287 | |
| 288 /** | |
| 289 * Returns a [InsertDesc] describing where to insert a new library-related | |
| 290 * directive. | |
| 291 */ | |
| 292 CorrectionUtils_InsertDesc getInsertDescImport() { | |
| 293 // analyze directives | |
| 294 Directive prevDirective = null; | |
| 295 for (Directive directive in unit.directives) { | |
| 296 if (directive is LibraryDirective || | |
| 297 directive is ImportDirective || | |
| 298 directive is ExportDirective) { | |
| 299 prevDirective = directive; | |
| 300 } | |
| 301 } | |
| 302 // insert after last library-related directive | |
| 303 if (prevDirective != null) { | |
| 304 CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc(); | |
| 305 result.offset = prevDirective.end; | |
| 306 String eol = endOfLine; | |
| 307 if (prevDirective is LibraryDirective) { | |
| 308 result.prefix = "${eol}${eol}"; | |
| 309 } else { | |
| 310 result.prefix = eol; | |
| 311 } | |
| 312 return result; | |
| 313 } | |
| 314 // no directives, use "top" location | |
| 315 return getInsertDescTop(); | |
| 316 } | |
| 317 | |
| 318 /** | |
| 319 * Returns a [InsertDesc] describing where to insert a new 'part' directive. | |
| 320 */ | |
| 321 CorrectionUtils_InsertDesc getInsertDescPart() { | |
| 322 // analyze directives | |
| 323 Directive prevDirective = null; | |
| 324 for (Directive directive in unit.directives) { | |
| 325 prevDirective = directive; | |
| 326 } | |
| 327 // insert after last directive | |
| 328 if (prevDirective != null) { | |
| 329 CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc(); | |
| 330 result.offset = prevDirective.end; | |
| 331 String eol = endOfLine; | |
| 332 if (prevDirective is PartDirective) { | |
| 333 result.prefix = eol; | |
| 334 } else { | |
| 335 result.prefix = "${eol}${eol}"; | |
| 336 } | |
| 337 return result; | |
| 338 } | |
| 339 // no directives, use "top" location | |
| 340 return getInsertDescTop(); | |
| 341 } | |
| 342 | |
| 343 /** | |
| 344 * Returns a [InsertDesc] describing where to insert a new directive or a | |
| 345 * top-level declaration at the top of the file. | |
| 346 */ | |
| 347 CorrectionUtils_InsertDesc getInsertDescTop() { | |
| 348 // skip leading line comments | |
| 349 int offset = 0; | |
| 350 bool insertEmptyLineBefore = false; | |
| 351 bool insertEmptyLineAfter = false; | |
| 352 String source = _buffer; | |
| 353 // skip hash-bang | |
| 354 if (offset < source.length - 2) { | |
| 355 String linePrefix = getText(offset, 2); | |
| 356 if (linePrefix == "#!") { | |
| 357 insertEmptyLineBefore = true; | |
| 358 offset = getLineNext(offset); | |
| 359 // skip empty lines to first line comment | |
| 360 int emptyOffset = offset; | |
| 361 while (emptyOffset < source.length - 2) { | |
| 362 int nextLineOffset = getLineNext(emptyOffset); | |
| 363 String line = source.substring(emptyOffset, nextLineOffset); | |
| 364 if (line.trim().isEmpty) { | |
| 365 emptyOffset = nextLineOffset; | |
| 366 continue; | |
| 367 } else if (line.startsWith("//")) { | |
| 368 offset = emptyOffset; | |
| 369 break; | |
| 370 } else { | |
| 371 break; | |
| 372 } | |
| 373 } | |
| 374 } | |
| 375 } | |
| 376 // skip line comments | |
| 377 while (offset < source.length - 2) { | |
| 378 String linePrefix = getText(offset, 2); | |
| 379 if (linePrefix == "//") { | |
| 380 insertEmptyLineBefore = true; | |
| 381 offset = getLineNext(offset); | |
| 382 } else { | |
| 383 break; | |
| 384 } | |
| 385 } | |
| 386 // determine if empty line is required after | |
| 387 int nextLineOffset = getLineNext(offset); | |
| 388 String insertLine = source.substring(offset, nextLineOffset); | |
| 389 if (!insertLine.trim().isEmpty) { | |
| 390 insertEmptyLineAfter = true; | |
| 391 } | |
| 392 // fill InsertDesc | |
| 393 CorrectionUtils_InsertDesc desc = new CorrectionUtils_InsertDesc(); | |
| 394 desc.offset = offset; | |
| 395 if (insertEmptyLineBefore) { | |
| 396 desc.prefix = endOfLine; | |
| 397 } | |
| 398 if (insertEmptyLineAfter) { | |
| 399 desc.suffix = endOfLine; | |
| 400 } | |
| 401 return desc; | |
| 402 } | |
| 403 | |
| 404 /** | |
| 405 * Skips whitespace characters and single EOL on the right from [index]. | |
| 406 * | |
| 407 * If [index] the end of a statement or method, then in the most cases it is | |
| 408 * a start of the next line. | |
| 409 */ | |
| 410 int getLineContentEnd(int index) { | |
| 411 int length = _buffer.length; | |
| 412 // skip whitespace characters | |
| 413 while (index < length) { | |
| 414 int c = _buffer.codeUnitAt(index); | |
| 415 if (!isWhitespace(c) || c == 0x0D || c == 0x0A) { | |
| 416 break; | |
| 417 } | |
| 418 index++; | |
| 419 } | |
| 420 // skip single \r | |
| 421 if (index < length && _buffer.codeUnitAt(index) == 0x0D) { | |
| 422 index++; | |
| 423 } | |
| 424 // skip single \n | |
| 425 if (index < length && _buffer.codeUnitAt(index) == 0x0A) { | |
| 426 index++; | |
| 427 } | |
| 428 // done | |
| 429 return index; | |
| 430 } | |
| 431 | |
| 432 /** | |
| 433 * Skips spaces and tabs on the left from [index]. | |
| 434 * | |
| 435 * If [index] is the start or a statement, then in the most cases it is a | |
| 436 * start on its line. | |
| 437 */ | |
| 438 int getLineContentStart(int index) { | |
| 439 while (index > 0) { | |
| 440 int c = _buffer.codeUnitAt(index - 1); | |
| 441 if (!isSpace(c)) { | |
| 442 break; | |
| 443 } | |
| 444 index--; | |
| 445 } | |
| 446 return index; | |
| 447 } | |
| 448 | |
| 449 /** | |
| 450 * Returns a start index of the next line after the line which contains the | |
| 451 * given index. | |
| 452 */ | |
| 453 int getLineNext(int index) { | |
| 454 int length = _buffer.length; | |
| 455 // skip to the end of the line | |
| 456 while (index < length) { | |
| 457 int c = _buffer.codeUnitAt(index); | |
| 458 if (c == 0xD || c == 0xA) { | |
| 459 break; | |
| 460 } | |
| 461 index++; | |
| 462 } | |
| 463 // skip single \r | |
| 464 if (index < length && _buffer.codeUnitAt(index) == 0xD) { | |
| 465 index++; | |
| 466 } | |
| 467 // skip single \n | |
| 468 if (index < length && _buffer.codeUnitAt(index) == 0xA) { | |
| 469 index++; | |
| 470 } | |
| 471 // done | |
| 472 return index; | |
| 473 } | |
| 474 | |
| 475 /** | |
| 476 * Returns the whitespace prefix of the line which contains given offset. | |
| 477 */ | |
| 478 String getLinePrefix(int index) { | |
| 479 int lineStart = getLineThis(index); | |
| 480 int length = _buffer.length; | |
| 481 int lineNonWhitespace = lineStart; | |
| 482 while (lineNonWhitespace < length) { | |
| 483 int c = _buffer.codeUnitAt(lineNonWhitespace); | |
| 484 if (c == 0xD || c == 0xA) { | |
| 485 break; | |
| 486 } | |
| 487 if (!isWhitespace(c)) { | |
| 488 break; | |
| 489 } | |
| 490 lineNonWhitespace++; | |
| 491 } | |
| 492 return getText(lineStart, lineNonWhitespace - lineStart); | |
| 493 } | |
| 494 | |
| 495 /** | |
| 496 * Returns the start index of the line which contains given index. | |
| 497 */ | |
| 498 int getLineThis(int index) { | |
| 499 while (index > 0) { | |
| 500 int c = _buffer.codeUnitAt(index - 1); | |
| 501 if (c == 0xD || c == 0xA) { | |
| 502 break; | |
| 503 } | |
| 504 index--; | |
| 505 } | |
| 506 return index; | |
| 507 } | |
| 508 | |
| 509 /** | |
| 510 * Returns a [SourceRange] that covers [range] and extends (if possible) to | |
| 511 * cover whole lines. | |
| 512 */ | |
| 513 SourceRange getLinesRange(SourceRange range) { | |
| 514 // start | |
| 515 int startOffset = range.offset; | |
| 516 int startLineOffset = getLineContentStart(startOffset); | |
| 517 // end | |
| 518 int endOffset = range.end; | |
| 519 int afterEndLineOffset = getLineContentEnd(endOffset); | |
| 520 // range | |
| 521 return rangeStartEnd(startLineOffset, afterEndLineOffset); | |
| 522 } | |
| 523 | |
| 524 /** | |
| 525 * Returns a [SourceRange] that covers all the given [Statement]s. | |
| 526 */ | |
| 527 SourceRange getLinesRangeStatements(List<Statement> statements) { | |
| 528 SourceRange range = rangeNodes(statements); | |
| 529 return getLinesRange(range); | |
| 530 } | |
| 531 | |
| 532 /** | |
| 533 * Returns the line prefix consisting of spaces and tabs on the left from the
given | |
| 534 * [AstNode]. | |
| 535 */ | |
| 536 String getNodePrefix(AstNode node) { | |
| 537 int offset = node.offset; | |
| 538 // function literal is special, it uses offset of enclosing line | |
| 539 if (node is FunctionExpression) { | |
| 540 return getLinePrefix(offset); | |
| 541 } | |
| 542 // use just prefix directly before node | |
| 543 return getPrefix(offset); | |
| 544 } | |
| 545 | |
| 546 /** | |
| 547 * Returns the text of the given [AstNode] in the unit. | |
| 548 */ | |
| 549 String getNodeText(AstNode node) { | |
| 550 return getText(node.offset, node.length); | |
| 551 } | |
| 552 | |
| 553 /** | |
| 554 * @return the source for the parameter with the given type and name. | |
| 555 */ | |
| 556 String getParameterSource(DartType type, String name) { | |
| 557 // no type | |
| 558 if (type == null || type.isDynamic) { | |
| 559 return name; | |
| 560 } | |
| 561 // function type | |
| 562 if (type is FunctionType) { | |
| 563 FunctionType functionType = type; | |
| 564 StringBuffer sb = new StringBuffer(); | |
| 565 // return type | |
| 566 DartType returnType = functionType.returnType; | |
| 567 if (returnType != null && !returnType.isDynamic) { | |
| 568 sb.write(getTypeSource(returnType)); | |
| 569 sb.write(' '); | |
| 570 } | |
| 571 // parameter name | |
| 572 sb.write(name); | |
| 573 // parameters | |
| 574 sb.write('('); | |
| 575 List<ParameterElement> fParameters = functionType.parameters; | |
| 576 for (int i = 0; i < fParameters.length; i++) { | |
| 577 ParameterElement fParameter = fParameters[i]; | |
| 578 if (i != 0) { | |
| 579 sb.write(", "); | |
| 580 } | |
| 581 sb.write(getParameterSource(fParameter.type, fParameter.name)); | |
| 582 } | |
| 583 sb.write(')'); | |
| 584 // done | |
| 585 return sb.toString(); | |
| 586 } | |
| 587 // simple type | |
| 588 return "${getTypeSource(type)} ${name}"; | |
| 589 } | |
| 590 | |
| 591 /** | |
| 592 * Returns the line prefix consisting of spaces and tabs on the left from the | |
| 593 * given offset. | |
| 594 */ | |
| 595 String getPrefix(int endIndex) { | |
| 596 int startIndex = getLineContentStart(endIndex); | |
| 597 return _buffer.substring(startIndex, endIndex); | |
| 598 } | |
| 599 | |
| 600 /** | |
| 601 * Returns the text of the given range in the unit. | |
| 602 */ | |
| 603 String getRangeText(SourceRange range) { | |
| 604 return getText(range.offset, range.length); | |
| 605 } | |
| 606 | |
| 607 /** | |
| 608 * Returns the text of the given range in the unit. | |
| 609 */ | |
| 610 String getText(int offset, int length) { | |
| 611 return _buffer.substring(offset, offset + length); | |
| 612 } | |
| 613 | |
| 614 /** | |
| 615 * Returns the source to reference [type] in this [CompilationUnit]. | |
| 616 */ | |
| 617 String getTypeSource(DartType type) { | |
| 618 StringBuffer sb = new StringBuffer(); | |
| 619 // just some Function, maybe find Function Type Alias later | |
| 620 if (type is FunctionType) { | |
| 621 return "Function"; | |
| 622 } | |
| 623 // prepare element | |
| 624 Element element = type.element; | |
| 625 if (element == null) { | |
| 626 String source = type.toString(); | |
| 627 source = source.replaceAll('<dynamic>', ''); | |
| 628 source = source.replaceAll('<dynamic, dynamic>', ''); | |
| 629 return source; | |
| 630 } | |
| 631 // append prefix | |
| 632 { | |
| 633 ImportElement imp = _getImportElement(element); | |
| 634 if (imp != null && imp.prefix != null) { | |
| 635 sb.write(imp.prefix.displayName); | |
| 636 sb.write("."); | |
| 637 } | |
| 638 } | |
| 639 // append simple name | |
| 640 String name = element.displayName; | |
| 641 sb.write(name); | |
| 642 // may be type arguments | |
| 643 if (type is InterfaceType) { | |
| 644 InterfaceType interfaceType = type; | |
| 645 List<DartType> arguments = interfaceType.typeArguments; | |
| 646 // check if has arguments | |
| 647 bool hasArguments = false; | |
| 648 for (DartType argument in arguments) { | |
| 649 if (!argument.isDynamic) { | |
| 650 hasArguments = true; | |
| 651 break; | |
| 652 } | |
| 653 } | |
| 654 // append type arguments | |
| 655 if (hasArguments) { | |
| 656 sb.write("<"); | |
| 657 for (int i = 0; i < arguments.length; i++) { | |
| 658 DartType argument = arguments[i]; | |
| 659 if (i != 0) { | |
| 660 sb.write(", "); | |
| 661 } | |
| 662 sb.write(getTypeSource(argument)); | |
| 663 } | |
| 664 sb.write(">"); | |
| 665 } | |
| 666 } | |
| 667 // done | |
| 668 return sb.toString(); | |
| 669 } | |
| 670 | |
| 671 /** | |
| 672 * Indents given source left or right. | |
| 673 */ | |
| 674 String indentSourceLeftRight(String source, bool right) { | |
| 675 StringBuffer sb = new StringBuffer(); | |
| 676 String indent = getIndent(1); | |
| 677 String eol = endOfLine; | |
| 678 List<String> lines = source.split(eol); | |
| 679 for (int i = 0; i < lines.length; i++) { | |
| 680 String line = lines[i]; | |
| 681 // last line, stop if empty | |
| 682 if (i == lines.length - 1 && isEmpty(line)) { | |
| 683 break; | |
| 684 } | |
| 685 // update line | |
| 686 if (right) { | |
| 687 line = "${indent}${line}"; | |
| 688 } else { | |
| 689 line = removeStart(line, indent); | |
| 690 } | |
| 691 // append line | |
| 692 sb.write(line); | |
| 693 sb.write(eol); | |
| 694 } | |
| 695 return sb.toString(); | |
| 696 } | |
| 697 | |
| 698 /** | |
| 699 * @return the source of the inverted condition for the given logical expressi
on. | |
| 700 */ | |
| 701 String invertCondition(Expression expression) => | |
| 702 _invertCondition0(expression)._source; | |
| 703 | |
| 704 /** | |
| 705 * Returns the source with indentation changed from [oldIndent] to | |
| 706 * [newIndent], keeping indentation of lines relative to each other. | |
| 707 */ | |
| 708 String replaceSourceIndent(String source, String oldIndent, | |
| 709 String newIndent) { | |
| 710 // prepare STRING token ranges | |
| 711 List<SourceRange> lineRanges = []; | |
| 712 { | |
| 713 var token = unit.beginToken; | |
| 714 while (token != null && token.type != TokenType.EOF) { | |
| 715 if (token.type == TokenType.STRING) { | |
| 716 lineRanges.add(rangeToken(token)); | |
| 717 } | |
| 718 token = token.next; | |
| 719 } | |
| 720 } | |
| 721 // re-indent lines | |
| 722 StringBuffer sb = new StringBuffer(); | |
| 723 String eol = endOfLine; | |
| 724 List<String> lines = source.split(eol); | |
| 725 int lineOffset = 0; | |
| 726 for (int i = 0; i < lines.length; i++) { | |
| 727 String line = lines[i]; | |
| 728 // last line, stop if empty | |
| 729 if (i == lines.length - 1 && isEmpty(line)) { | |
| 730 break; | |
| 731 } | |
| 732 // check if "offset" is in one of the String ranges | |
| 733 bool inString = false; | |
| 734 for (SourceRange lineRange in lineRanges) { | |
| 735 if (lineOffset > lineRange.offset && lineOffset < lineRange.end) { | |
| 736 inString = true; | |
| 737 } | |
| 738 if (lineOffset > lineRange.end) { | |
| 739 break; | |
| 740 } | |
| 741 } | |
| 742 lineOffset += line.length + eol.length; | |
| 743 // update line indent | |
| 744 if (!inString) { | |
| 745 line = "${newIndent}${removeStart(line, oldIndent)}"; | |
| 746 } | |
| 747 // append line | |
| 748 sb.write(line); | |
| 749 sb.write(eol); | |
| 750 } | |
| 751 return sb.toString(); | |
| 752 } | |
| 753 | |
| 754 /** | |
| 755 * Returns the source of the given [SourceRange] with indentation changed | |
| 756 * from [oldIndent] to [newIndent], keeping indentation of lines relative | |
| 757 * to each other. | |
| 758 */ | |
| 759 String replaceSourceRangeIndent(SourceRange range, String oldIndent, | |
| 760 String newIndent) { | |
| 761 String oldSource = getRangeText(range); | |
| 762 return replaceSourceIndent(oldSource, oldIndent, newIndent); | |
| 763 } | |
| 764 | |
| 765 /** | |
| 766 * @return the [ImportElement] used to import given [Element] into [library]. | |
| 767 * May be `null` if was not imported, i.e. declared in the same librar
y. | |
| 768 */ | |
| 769 ImportElement _getImportElement(Element element) { | |
| 770 for (ImportElement imp in _library.imports) { | |
| 771 Map<String, Element> definedNames = getImportNamespace(imp); | |
| 772 if (definedNames.containsValue(element)) { | |
| 773 return imp; | |
| 774 } | |
| 775 } | |
| 776 return null; | |
| 777 } | |
| 778 | |
| 779 /** | |
| 780 * @return the [InvertedCondition] for the given logical expression. | |
| 781 */ | |
| 782 _InvertedCondition _invertCondition0(Expression expression) { | |
| 783 if (expression is BooleanLiteral) { | |
| 784 BooleanLiteral literal = expression; | |
| 785 if (literal.value) { | |
| 786 return _InvertedCondition._simple("false"); | |
| 787 } else { | |
| 788 return _InvertedCondition._simple("true"); | |
| 789 } | |
| 790 } | |
| 791 if (expression is BinaryExpression) { | |
| 792 BinaryExpression binary = expression; | |
| 793 TokenType operator = binary.operator.type; | |
| 794 Expression le = binary.leftOperand; | |
| 795 Expression re = binary.rightOperand; | |
| 796 _InvertedCondition ls = _invertCondition0(le); | |
| 797 _InvertedCondition rs = _invertCondition0(re); | |
| 798 if (operator == TokenType.LT) { | |
| 799 return _InvertedCondition._binary2(ls, " >= ", rs); | |
| 800 } | |
| 801 if (operator == TokenType.GT) { | |
| 802 return _InvertedCondition._binary2(ls, " <= ", rs); | |
| 803 } | |
| 804 if (operator == TokenType.LT_EQ) { | |
| 805 return _InvertedCondition._binary2(ls, " > ", rs); | |
| 806 } | |
| 807 if (operator == TokenType.GT_EQ) { | |
| 808 return _InvertedCondition._binary2(ls, " < ", rs); | |
| 809 } | |
| 810 if (operator == TokenType.EQ_EQ) { | |
| 811 return _InvertedCondition._binary2(ls, " != ", rs); | |
| 812 } | |
| 813 if (operator == TokenType.BANG_EQ) { | |
| 814 return _InvertedCondition._binary2(ls, " == ", rs); | |
| 815 } | |
| 816 if (operator == TokenType.AMPERSAND_AMPERSAND) { | |
| 817 return _InvertedCondition._binary( | |
| 818 TokenType.BAR_BAR.precedence, | |
| 819 ls, | |
| 820 " || ", | |
| 821 rs); | |
| 822 } | |
| 823 if (operator == TokenType.BAR_BAR) { | |
| 824 return _InvertedCondition._binary( | |
| 825 TokenType.AMPERSAND_AMPERSAND.precedence, | |
| 826 ls, | |
| 827 " && ", | |
| 828 rs); | |
| 829 } | |
| 830 } | |
| 831 if (expression is IsExpression) { | |
| 832 IsExpression isExpression = expression; | |
| 833 String expressionSource = getNodeText(isExpression.expression); | |
| 834 String typeSource = getNodeText(isExpression.type); | |
| 835 if (isExpression.notOperator == null) { | |
| 836 return _InvertedCondition._simple( | |
| 837 "${expressionSource} is! ${typeSource}"); | |
| 838 } else { | |
| 839 return _InvertedCondition._simple( | |
| 840 "${expressionSource} is ${typeSource}"); | |
| 841 } | |
| 842 } | |
| 843 if (expression is PrefixExpression) { | |
| 844 PrefixExpression prefixExpression = expression; | |
| 845 TokenType operator = prefixExpression.operator.type; | |
| 846 if (operator == TokenType.BANG) { | |
| 847 Expression operand = prefixExpression.operand; | |
| 848 while (operand is ParenthesizedExpression) { | |
| 849 ParenthesizedExpression pe = operand as ParenthesizedExpression; | |
| 850 operand = pe.expression; | |
| 851 } | |
| 852 return _InvertedCondition._simple(getNodeText(operand)); | |
| 853 } | |
| 854 } | |
| 855 if (expression is ParenthesizedExpression) { | |
| 856 ParenthesizedExpression pe = expression; | |
| 857 Expression innerExpresion = pe.expression; | |
| 858 while (innerExpresion is ParenthesizedExpression) { | |
| 859 innerExpresion = (innerExpresion as ParenthesizedExpression).expression; | |
| 860 } | |
| 861 return _invertCondition0(innerExpresion); | |
| 862 } | |
| 863 DartType type = expression.bestType; | |
| 864 if (type.displayName == "bool") { | |
| 865 return _InvertedCondition._simple("!${getNodeText(expression)}"); | |
| 866 } | |
| 867 return _InvertedCondition._simple(getNodeText(expression)); | |
| 868 } | |
| 869 } | |
| 870 | |
| 871 | |
| 872 /** | |
| 873 * Describes where to insert new directive or top-level declaration. | |
| 874 */ | |
| 875 class CorrectionUtils_InsertDesc { | |
| 876 int offset = 0; | |
| 877 String prefix = ""; | |
| 878 String suffix = ""; | |
| 879 } | |
| 880 | |
| 881 | |
| 882 /** | |
| 883 * A container with a source and its precedence. | |
| 884 */ | |
| 885 class _InvertedCondition { | |
| 886 final int _precedence; | |
| 887 | |
| 888 final String _source; | |
| 889 | |
| 890 _InvertedCondition(this._precedence, this._source); | |
| 891 | |
| 892 static _InvertedCondition _binary(int precedence, _InvertedCondition left, | |
| 893 String operation, _InvertedCondition right) { | |
| 894 String src = | |
| 895 _parenthesizeIfRequired(left, precedence) + | |
| 896 operation + | |
| 897 _parenthesizeIfRequired(right, precedence); | |
| 898 return new _InvertedCondition(precedence, src); | |
| 899 } | |
| 900 | |
| 901 static _InvertedCondition _binary2(_InvertedCondition left, String operation, | |
| 902 _InvertedCondition right) { | |
| 903 // TODO(scheglov) conside merging with "_binary()" after testing | |
| 904 return new _InvertedCondition( | |
| 905 1 << 20, | |
| 906 "${left._source}${operation}${right._source}"); | |
| 907 } | |
| 908 | |
| 909 /** | |
| 910 * Adds enclosing parenthesis if the precedence of the [_InvertedCondition] if
less than the | |
| 911 * precedence of the expression we are going it to use in. | |
| 912 */ | |
| 913 static String _parenthesizeIfRequired(_InvertedCondition expr, | |
| 914 int newOperatorPrecedence) { | |
| 915 if (expr._precedence < newOperatorPrecedence) { | |
| 916 return "(${expr._source})"; | |
| 917 } | |
| 918 return expr._source; | |
| 919 } | |
| 920 | |
| 921 static _InvertedCondition _simple(String source) => | |
| 922 new _InvertedCondition(2147483647, source); | |
| 923 } | |
| OLD | NEW |