001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 the ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package eu.cqse.check.framework.shallowparser.util; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.EnumSet; 022import java.util.List; 023import java.util.Optional; 024import java.util.Set; 025import java.util.function.Predicate; 026 027import org.conqat.lib.commons.collections.CollectionUtils; 028import org.conqat.lib.commons.collections.UnmodifiableList; 029import org.conqat.lib.commons.region.LineBasedRegion; 030import org.conqat.lib.commons.region.OffsetBasedRegion; 031 032import com.google.common.collect.ImmutableSet; 033 034import eu.cqse.check.framework.scanner.ELanguage; 035import eu.cqse.check.framework.scanner.ETokenType; 036import eu.cqse.check.framework.scanner.IToken; 037import eu.cqse.check.framework.shallowparser.SubTypeNames; 038import eu.cqse.check.framework.shallowparser.TokenStreamUtils; 039import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType; 040import eu.cqse.check.framework.shallowparser.framework.ShallowEntity; 041import eu.cqse.check.framework.shallowparser.framework.ShallowEntityTraversalUtils; 042 043/** 044 * Utility methods used for dealing with {@link ShallowEntity}s. 045 */ 046public class ShallowParsingUtils { 047 048 /** 049 * Entity subtypes that are part of a branching statement but not the first 050 * option of the branches. Those statements must not be separated from the first 051 * part of the branching statement when we consider refactoring suggestions. DO 052 * is part of this set because a last entity of a refactoring suggestion 053 * covering a do-while loop (where while is the branching statement) can be of 054 * type DO. 055 */ 056 private static final Set<String> SUBPART_BRANCHING_STATEMENTS = ImmutableSet.of(SubTypeNames.ELSE_IF, 057 SubTypeNames.ELSE, SubTypeNames.CATCH, SubTypeNames.FINALLY, SubTypeNames.CASE, SubTypeNames.DEFAULT, 058 SubTypeNames.USING, SubTypeNames.DO); 059 060 /** 061 * A set containing C# contextual keywords. A contextual keyword is used to 062 * provide a specific meaning in the code, but it is not a reserved word in C#. 063 * Thus, one could name a local variable or method using a contextual keyword. 064 */ 065 private static final Set<ETokenType> CONTEXTUAL_KEYWORDS = ImmutableSet.of(ETokenType.ADD, ETokenType.ASYNC, 066 ETokenType.AWAIT, ETokenType.DYNAMIC, ETokenType.GET, ETokenType.GLOBAL, ETokenType.PARTIAL, 067 ETokenType.REMOVE, ETokenType.SET, ETokenType.VALUE, ETokenType.VAR, ETokenType.WHEN, ETokenType.WHERE, 068 ETokenType.YIELD, ETokenType.GROUP); 069 070 /** 071 * EnumSet containing every language in which it is possible that the type info 072 * of a variable or parameter might be after a colon. 073 * <p> 074 * E.g. test(param1 : String) 075 */ 076 private static final EnumSet<ELanguage> LANGUAGES_TYPE_INFO_AFTER_COLON = EnumSet.of(ELanguage.JAVASCRIPT, 077 ELanguage.GOSU); 078 079 /** 080 * Entity subtypes that represent looping statements 081 */ 082 private static final Set<String> LOOP_SUBTYPES = ImmutableSet.of(SubTypeNames.FOR, SubTypeNames.FOREACH, 083 SubTypeNames.WHILE); 084 085 /** Lists all primitive statements (i.e. statements without children). */ 086 public static List<ShallowEntity> listPrimitiveStatements(Collection<ShallowEntity> entities) { 087 return new ShallowEntityTraversalUtils.CollectingVisitorBase() { 088 @Override 089 protected boolean collect(ShallowEntity entity) { 090 return entity.getType() == EShallowEntityType.STATEMENT && entity.getChildren().isEmpty(); 091 } 092 }.apply(entities); 093 } 094 095 /** Lists all nested statements (i.e. statements with children). */ 096 public static List<ShallowEntity> listNestedStatements(Collection<ShallowEntity> entities) { 097 return new ShallowEntityTraversalUtils.CollectingVisitorBase() { 098 @Override 099 protected boolean collect(ShallowEntity entity) { 100 UnmodifiableList<ShallowEntity> children = entity.getChildren(); 101 return entity.getType() == EShallowEntityType.STATEMENT && !children.isEmpty() 102 // only include this, if nested is also code and not an 103 // anonymous function, class, etc. 104 && children.get(0).getType() == EShallowEntityType.STATEMENT; 105 } 106 }.apply(entities); 107 } 108 109 /** 110 * Returns the tokens of a method entity that correspond to parameter names. 111 */ 112 public static List<IToken> extractParameterNameTokens(ShallowEntity entity) { 113 return extractVariableNameTokens( 114 TokenStreamUtils.tokensBetween(entity.ownStartTokens(), ETokenType.LPAREN, ETokenType.RPAREN), true); 115 } 116 117 /** Returns the tokens corresponding to variable names. */ 118 public static List<IToken> extractVariableNameTokens(List<IToken> tokens) { 119 return extractVariableNameTokens(tokens, false); 120 } 121 122 /** 123 * Returns the tokens corresponding to variable names. Heuristic is to look at 124 * identifier tokens outside of parenthesis/braces and directly before a comma, 125 * equals sign, or closing parenthesis. The equals sign is needed to deal with 126 * C++ default parameters and assignments in local variables. Additionally, we 127 * drop brackets, as they can obscure the type/name separation. 128 * 129 * @param ignoreParameterTypes 130 * if this is true, identifiers at the beginning, or after a comma or 131 * a double-colon are ignored, as they are likely type names in a 132 * parameter list (C++ allows to skip the parameter name in some 133 * cases). 134 */ 135 private static List<IToken> extractVariableNameTokens(List<IToken> tokens, boolean ignoreParameterTypes) { 136 List<IToken> result = new ArrayList<>(); 137 int parenthesisNesting = 0; 138 boolean waitForComma = false; 139 IToken previousToken = null; 140 ETokenType beforePreviousType = null; 141 for (IToken token : tokens) { 142 switch (token.getType()) { 143 case LBRACK: 144 case LT: 145 case LPAREN: 146 case LBRACE: 147 parenthesisNesting += 1; 148 break; 149 150 case RBRACK: 151 case GT: 152 case RPAREN: 153 case RBRACE: 154 parenthesisNesting -= 1; 155 // do not update previousToken 156 continue; 157 158 case COMMA: 159 case ARRAY_SEPARATOR: 160 // the commas in matlab methods are parsed as array separators 161 if (parenthesisNesting == 0) { 162 if (!waitForComma && isParameterName(previousToken, beforePreviousType, ignoreParameterTypes)) { 163 result.add(previousToken); 164 } 165 waitForComma = false; 166 } 167 break; 168 169 case EQ: 170 case EOL: 171 case EQUAL: 172 case SEMICOLON: 173 if (parenthesisNesting == 0 && !waitForComma 174 && isParameterName(previousToken, beforePreviousType, ignoreParameterTypes)) { 175 result.add(previousToken); 176 waitForComma = true; 177 } 178 break; 179 180 case COLON: 181 // In TypeScript you can add type information after colon 182 if (LANGUAGES_TYPE_INFO_AFTER_COLON.contains(token.getLanguage()) && parenthesisNesting == 0 183 && !waitForComma && isParameterName(previousToken, beforePreviousType, false)) { 184 result.add(previousToken); 185 waitForComma = true; 186 } 187 break; 188 189 case QUESTION: 190 // skip question mark (optional parameter token) between parameter name and 191 // colon (TypeScript) 192 token = previousToken; 193 break; 194 default: 195 break; 196 } 197 198 if (parenthesisNesting == 0) { 199 if (previousToken != null) { 200 beforePreviousType = previousToken.getType(); 201 } 202 previousToken = token; 203 } 204 } 205 206 if (parenthesisNesting == 0 && !waitForComma 207 && isParameterName(previousToken, beforePreviousType, ignoreParameterTypes)) { 208 result.add(previousToken); 209 } 210 211 return result; 212 } 213 214 /** Returns whether the previous token could be a parameter name. */ 215 private static boolean isParameterName(IToken previousToken, ETokenType beforePreviousType, 216 boolean ignoreParameterTypes) { 217 if (previousToken == null) { 218 return false; 219 } 220 221 if (previousToken.getLanguage() == ELanguage.JAVASCRIPT) { 222 return previousToken.getType() == ETokenType.IDENTIFIER && (!ignoreParameterTypes 223 || (beforePreviousType == ETokenType.COMMA || beforePreviousType == null)); 224 225 } 226 227 return (previousToken.getType() == ETokenType.IDENTIFIER || isCsContextualKeyword(previousToken)) 228 && (!ignoreParameterTypes || (beforePreviousType != ETokenType.COMMA && beforePreviousType != null 229 && beforePreviousType != ETokenType.SCOPE)); 230 } 231 232 private static boolean isCsContextualKeyword(IToken token) { 233 return token.getLanguage() == ELanguage.CS && CONTEXTUAL_KEYWORDS.contains(token.getType()); 234 } 235 236 /** 237 * Returns the list of tokens corresponding to the names of variables newly 238 * declared within a for loop. 239 */ 240 public static List<IToken> extractVariablesDeclaredInFor(ShallowEntity entity) { 241 List<IToken> forLoopInitTokens = TokenStreamUtils.tokensBetween(entity.ownStartTokens(), ETokenType.LPAREN, 242 ETokenType.SEMICOLON); 243 List<IToken> variableNameTokens = ShallowParsingUtils.extractVariableNameTokens(forLoopInitTokens); 244 245 // handle the case where only existing variables are initialized 246 if (!variableNameTokens.isEmpty() && variableNameTokens.get(0) == forLoopInitTokens.get(0)) { 247 variableNameTokens.clear(); 248 } 249 250 return variableNameTokens; 251 } 252 253 /** Returns whether the entity is a local variable. */ 254 public static boolean isLocalVariable(ShallowEntity entity) { 255 return entity.getType() == EShallowEntityType.STATEMENT 256 && SubTypeNames.LOCAL_VARIABLE.equals(entity.getSubtype()); 257 } 258 259 /** Returns whether the entity is a global variable. */ 260 public static boolean isGlobalVariable(ShallowEntity entity) { 261 return entity.getType() == EShallowEntityType.ATTRIBUTE 262 && (entity.getParent() == null || entity.getParent().getType() == EShallowEntityType.MODULE); 263 } 264 265 /** 266 * Checks if <code>continue</code> or <code>break</code> are contained in a 267 * <code>for</code> or <code>while</code> loop in the given block. If there is 268 * <code>continue</code>, <code>break</code> or <code>return;</code> in a code 269 * segment, it can not be extracted. (Note: <code>return sth;</code> may be 270 * extractable.) 271 * 272 * @param block 273 * the {@link ShallowEntity ShallowEntities} that will be checked. 274 * @return <code>true</code> if there is <code>continue</code> or 275 * <code>break</code> 276 */ 277 public static boolean containsLoopReferenceWithoutLoop(List<ShallowEntity> block) { 278 for (ShallowEntity statement : block) { 279 if (statement.getSubtype().equals(SubTypeNames.FOR) || statement.getSubtype().equals(SubTypeNames.WHILE)) { 280 continue; 281 } 282 List<IToken> startTokens = statement.ownStartTokens(); 283 IToken firstToken = startTokens.get(0); 284 if (firstToken.getType() == ETokenType.CONTINUE || firstToken.getType() == ETokenType.BREAK) { 285 return true; 286 } else if (firstToken.getType() == ETokenType.RETURN) { 287 // return statements are not extractable (for us), even if 288 // return obj; 289 // may be extractable if it is at the very end of the original 290 // method 291 return true; 292 } 293 } 294 for (ShallowEntity statement : block) { 295 // when the statement contains a whole anonymous class or lambda, loops don't 296 // affect the behavior and can be ignored. 297 if (containsAnonymousClassOrLambda(statement)) { 298 continue; 299 } 300 if (containsLoopReferenceWithoutLoop(statement.getChildren())) { 301 return true; 302 } 303 } 304 return false; 305 } 306 307 /** 308 * Checks if the child of the statement is an anonymous class or lambda 309 * expression 310 * 311 * @param statement 312 * the {@link ShallowEntity} that will be checked. 313 * @return <code>true</code> if there is an anonymous class or lambda. 314 */ 315 private static boolean containsAnonymousClassOrLambda(ShallowEntity statement) { 316 return !statement.getChildren().isEmpty() 317 && (statement.getChildren().get(0).getSubtype().equals(SubTypeNames.ANONYMOUS_CLASS) 318 || statement.getChildren().get(0).getSubtype().equals(SubTypeNames.LAMBDA)); 319 } 320 321 /** 322 * Returns for a given entity that contains an <code>if</code> or 323 * <code>try</code> block a list of {@link ShallowEntity ShallowEntities} that 324 * represents the corresponding <code>else if</code> or <code>catch</code> and 325 * <code>else</code> or <code>finally</code> block. That means that the complete 326 * statement is determined and returned. 327 * 328 * @param shallowEntity 329 * the entity that contains an <code>if</code> or <code>try</code> 330 * block 331 * @return The list of {@link ShallowEntity ShallowEntities} that contains the 332 * corresponding block with all <code>else if</code> or 333 * <code>catch</code> and <code>else</code> or <code>finally</code> 334 * entities belonging to the given shallowEntity 335 */ 336 public static List<ShallowEntity> getCompleteStatement(ShallowEntity shallowEntity) { 337 338 String secondKeyword = ""; 339 String thirdKeyword = ""; 340 341 if (shallowEntity.getSubtype().equals(SubTypeNames.IF)) { 342 secondKeyword = SubTypeNames.ELSE_IF; 343 thirdKeyword = SubTypeNames.ELSE; 344 } else if (shallowEntity.getSubtype().equals(SubTypeNames.TRY)) { 345 secondKeyword = SubTypeNames.CATCH; 346 thirdKeyword = SubTypeNames.FINALLY; 347 } else if (shallowEntity.getSubtype().equals(SubTypeNames.SWITCH)) { 348 secondKeyword = SubTypeNames.CASE; 349 thirdKeyword = SubTypeNames.DEFAULT; 350 } else { 351 return CollectionUtils.emptyList(); 352 } 353 354 return resembleCompleteStatement(shallowEntity, secondKeyword, thirdKeyword); 355 } 356 357 /** 358 * For a given {@link ShallowEntity} of subtype <code>if</code>, 359 * <code>try</code> or <code>switch</code> and the corresponding keywords the 360 * complete statement is resembled 361 * 362 * @param shallowEntity 363 * A {@link ShallowEntity} of subtype <code>if</code>, 364 * <code>try</code> or <code>switch</code>. 365 * @param secondKeyword 366 * The corresponding second keyword, for <code>if</code> that is 367 * <code>else if</code>. 368 * @param thirdKeyword 369 * The corresponding third keyword, for <code>if</code> that is 370 * <code>else</code>. 371 * @return the complete statement 372 */ 373 private static List<ShallowEntity> resembleCompleteStatement(ShallowEntity shallowEntity, String secondKeyword, 374 String thirdKeyword) { 375 List<ShallowEntity> completeStatement = new ArrayList<>(); 376 completeStatement.add(shallowEntity); 377 List<ShallowEntity> children = shallowEntity.getParent().getChildren(); 378 379 boolean firstKeywordFound = false; 380 // Construct the complete statement: First if/try, then as many else 381 // if/catch as you need and finally (optionally) one else/finally 382 // statement. 383 for (ShallowEntity child : children) { 384 if (!firstKeywordFound) { 385 if (child.equals(shallowEntity)) { 386 firstKeywordFound = true; 387 } 388 } else if (child.getSubtype().equals(secondKeyword)) { 389 completeStatement.add(child); 390 391 } else if (child.getSubtype().equals(thirdKeyword)) { 392 completeStatement.add(child); 393 return completeStatement; 394 } else { 395 break; 396 } 397 } 398 399 return completeStatement; 400 } 401 402 /** 403 * Returns the entity which is directly after the given {@link ShallowEntity}. 404 * 405 * @param candidate 406 * the last entity of the candidate 407 * @return The first entity which begins after the the given 408 * {@link ShallowEntity}. If there is no such entity, <code>null</code> 409 * will be returned. 410 */ 411 public static ShallowEntity getFirstEntityAfter(ShallowEntity candidate) { 412 List<ShallowEntity> childrenOfParent = candidate.getParent().getChildrenOfType(EShallowEntityType.STATEMENT); 413 414 int indexOfNextCandidate = childrenOfParent.indexOf(candidate) + 1; 415 if (indexOfNextCandidate < childrenOfParent.size()) { 416 return childrenOfParent.get(indexOfNextCandidate); 417 } 418 return null; 419 } 420 421 /** 422 * Returns the entity which is directly before the given {@link ShallowEntity}. 423 * 424 * @param candidate 425 * the first entity of the candidate 426 * @return The last entity which ends before the the given 427 * {@link ShallowEntity}. If there is no such entity, <code>null</code> 428 * will be returned. 429 */ 430 public static ShallowEntity getLastEntityBefore(ShallowEntity candidate) { 431 List<ShallowEntity> childrenOfParent = candidate.getParent().getChildrenOfType(EShallowEntityType.STATEMENT); 432 433 int indexOfPreviousCandidate = childrenOfParent.indexOf(candidate) - 1; 434 if (indexOfPreviousCandidate >= 0) { 435 return childrenOfParent.get(indexOfPreviousCandidate); 436 } 437 return null; 438 } 439 440 /** 441 * Returns the nesting area of a list of {@link ShallowEntity ShallowEntities}. 442 * The nesting area is the sum of the nesting depth of all {@link ShallowEntity 443 * ShallowEntities}. 444 * 445 * @param shallowEntities 446 * the {@link ShallowEntity ShallowEntities}. 447 * @return The nesting area of the given entities. 448 */ 449 public static int getNestingArea(List<ShallowEntity> shallowEntities) { 450 int currentDepth = 0; 451 int nestingArea = 0; 452 for (ShallowEntity shallowEntity : shallowEntities) { 453 nestingArea += getNestingArea(shallowEntity, currentDepth); 454 } 455 return nestingArea; 456 } 457 458 /** 459 * Returns the nesting area of a {@link ShallowEntity}. The nesting area is the 460 * sum of the nesting depth of all {@link ShallowEntity ShallowEntities} . 461 * 462 * @param shallowEntity 463 * the {@link ShallowEntity}. 464 * @return The nesting area of the given entities. 465 */ 466 private static int getNestingArea(ShallowEntity shallowEntity, int currentDepth) { 467 int nestingArea = currentDepth; 468 for (ShallowEntity child : shallowEntity.getChildrenOfType(EShallowEntityType.STATEMENT)) { 469 nestingArea += getNestingArea(child, currentDepth + 1); 470 } 471 return nestingArea; 472 } 473 474 /** 475 * Returns the maximal nesting depth of the given block. 476 * 477 * @param block 478 * the {@link ShallowEntity ShallowEntities} that are taken into 479 * account for the maximal nesting depth. 480 * @return The maximal nesting depth 481 */ 482 public static int getNestingDepth(List<ShallowEntity> block) { 483 int maxNestingDepth = 0; 484 for (ShallowEntity shallowEntity : block) { 485 int entityNestingDepth = getNestingDepth(shallowEntity); 486 487 if (entityNestingDepth > maxNestingDepth) { 488 maxNestingDepth = entityNestingDepth; 489 } 490 } 491 return maxNestingDepth; 492 } 493 494 /** 495 * Returns the maximal nesting depth of the given entity. 496 * 497 * @param shallowEntity 498 * the {@link ShallowEntity}. 499 * @return The maximal nesting depth 500 */ 501 public static int getNestingDepth(ShallowEntity shallowEntity) { 502 int maxNestingDepth = 0; 503 for (ShallowEntity child : shallowEntity.getChildrenOfType(EShallowEntityType.STATEMENT)) { 504 int childNestingDepth = getNestingDepth(child); 505 506 if (maxNestingDepth <= childNestingDepth) { 507 maxNestingDepth = childNestingDepth + 1; 508 } 509 } 510 return maxNestingDepth; 511 } 512 513 /** 514 * Returns the number of blank or commented lines after the given 515 * {@link ShallowEntity}. 516 * 517 * @param candidate 518 * if a refactoring candidate should be considered, take its last 519 * {@link ShallowEntity}. 520 * @return The number of blank or commented lines 521 */ 522 public static int getNumberOfBlankLinesOrCommentsAfter(ShallowEntity candidate) { 523 int lineDifference = 1; 524 ShallowEntity firstEntityAfter = getFirstEntityAfter(candidate); 525 if (firstEntityAfter == null) { 526 if (candidate.getParent().getEndLine() - candidate.getEndLine() > 1) { 527 lineDifference = candidate.getParent().getEndLine() - candidate.getEndLine(); 528 } 529 return lineDifference - 1; 530 } 531 lineDifference = firstEntityAfter.getStartLine() - candidate.getEndLine(); 532 return lineDifference - 1; 533 } 534 535 /** 536 * Returns the number of blank or commented lines before the given 537 * {@link ShallowEntity}. 538 * 539 * @param candidate 540 * if a refactoring candidate should be considered, take its first 541 * {@link ShallowEntity}. 542 * @return The number of blank or commented lines 543 */ 544 public static int getNumberOfBlankLinesOrCommentsBefore(ShallowEntity candidate) { 545 int lineDifference = 1; 546 ShallowEntity lastEntityBefore = getLastEntityBefore(candidate); 547 if (lastEntityBefore == null) { 548 if (candidate.getStartLine() - candidate.getParent().getStartLine() > 1) { 549 lineDifference = candidate.getStartLine() - candidate.getParent().getStartLine(); 550 } 551 return lineDifference - 1; 552 } 553 lineDifference = candidate.getStartLine() - lastEntityBefore.getEndLine(); 554 return lineDifference - 1; 555 } 556 557 /** 558 * Returns a list of variable names (identifiers) that must be returned from the 559 * specified block if it is extracted into a new method 560 * 561 * @param block 562 * {@link ShallowEntity ShallowEntities} that might be extracted 563 * @return List of identifiers 564 */ 565 public static List<String> getReturnParameters(List<ShallowEntity> block) { 566 List<String> returnParameters = new ArrayList<>(); 567 List<ShallowEntity> allEntities = ShallowEntityTraversalUtils.listEntitiesOfType(block, 568 EShallowEntityType.STATEMENT); 569 for (ShallowEntity statement : allEntities) { 570 if (statement.includedTokens().get(0).getType() == ETokenType.RETURN 571 // if the return statement is located in an anonymous class or lambda, it can be 572 // ignored because its not a return parameter of the block. 573 && !locatedInAnonymousClassOrLambda(statement)) { 574 returnParameters.add(statement.getName()); 575 } 576 } 577 return returnParameters; 578 } 579 580 /** 581 * Recursive computation if the statement is located in an anonymous class or 582 * lambda. 583 * 584 * @param statement 585 * {@link ShallowEntity} that represents the statement 586 * @return whether the statement is located in an anonymous class or lambda 587 */ 588 private static boolean locatedInAnonymousClassOrLambda(ShallowEntity statement) { 589 ShallowEntity parent = statement.getParent(); 590 if (parent == null) { 591 // exit condition: the outer statement is reached, so the statement can't be in 592 // an anonymous class 593 return false; 594 } else if (parent.getSubtype().equals(SubTypeNames.ANONYMOUS_CLASS) 595 || parent.getSubtype().equals(SubTypeNames.LAMBDA)) { 596 return true; 597 } 598 return locatedInAnonymousClassOrLambda(parent); 599 } 600 601 /** Returns all statements in the given entity */ 602 public static List<ShallowEntity> getStatements(ShallowEntity entity) { 603 List<ShallowEntity> list = new ArrayList<>(); 604 list.add(entity); 605 return ShallowEntityTraversalUtils.listEntitiesOfType(list, EShallowEntityType.STATEMENT); 606 } 607 608 /** 609 * Obtain all classes (including anonymous classes) that contain directly or 610 * indirectly the method corresponding to the given line numbers. 611 * 612 * @param shallowEntities 613 * the {@link ShallowEntity ShallowEntities} that represent the file 614 * in which the finding was found 615 * @param startLine 616 * the start line of the code area that must be covered by the 617 * classes 618 * @param endLine 619 * the end line of the code area that must be covered by the classes 620 * @return A list that contains at least one {@link ShallowEntity} with type 621 * TYPE. 622 */ 623 public static List<ShallowEntity> getClassEntities(List<ShallowEntity> shallowEntities, int startLine, 624 int endLine) { 625 626 List<ShallowEntity> classEntities = ShallowEntityTraversalUtils.listEntitiesOfType(shallowEntities, 627 EShallowEntityType.TYPE); 628 629 List<ShallowEntity> classEntitiesCoveringCodeArea = new ArrayList<>(); 630 for (ShallowEntity classEntity : classEntities) { 631 if (classEntity.getStartLine() <= startLine && classEntity.getEndLine() >= endLine) { 632 classEntitiesCoveringCodeArea.add(classEntity); 633 } 634 } 635 return classEntitiesCoveringCodeArea; 636 } 637 638 /** 639 * Obtain all methods. 640 * 641 * @param shallowEntities 642 * the {@link ShallowEntity ShallowEntities} that represent the file 643 * in which the finding was found 644 * @return all entities of type {@link EShallowEntityType#METHOD} 645 */ 646 public static List<ShallowEntity> getMethods(List<ShallowEntity> shallowEntities) { 647 return ShallowEntityTraversalUtils.listEntitiesOfType(shallowEntities, EShallowEntityType.METHOD); 648 } 649 650 /** 651 * Get a method by its region. The region must match exactly. 652 * 653 * @param shallowEntities 654 * the {@link ShallowEntity ShallowEntities} that represent the file 655 * in which the finding was found 656 * @param region 657 * of the method 658 */ 659 public static Optional<ShallowEntity> getMethod(List<ShallowEntity> shallowEntities, OffsetBasedRegion region) { 660 Predicate<ShallowEntity> filter = (methodEntity) -> methodEntity.getStartOffset() == region.getStart() 661 && methodEntity.getEndOffset() == region.getEnd(); 662 return getMethod(shallowEntities, filter); 663 } 664 665 /** 666 * Get a method by its region. The region must match exactly. 667 * 668 * @param shallowEntities 669 * the {@link ShallowEntity ShallowEntities} that represent the file 670 * in which the finding was found 671 * @param region 672 * of the method 673 */ 674 public static Optional<ShallowEntity> getMethod(List<ShallowEntity> shallowEntities, LineBasedRegion region) { 675 Predicate<ShallowEntity> filter = (methodEntity) -> methodEntity.getStartLine() == region.getStart() 676 && methodEntity.getEndLine() == region.getEnd(); 677 return getMethod(shallowEntities, filter); 678 } 679 680 /** 681 * Get a method using a predicate. 682 * 683 * @param shallowEntities 684 * the {@link ShallowEntity ShallowEntities} that represent the file 685 * in which the finding was found 686 * @param filter 687 * to filter a method 688 */ 689 public static Optional<ShallowEntity> getMethod(List<ShallowEntity> shallowEntities, 690 Predicate<ShallowEntity> filter) { 691 return getMethods(shallowEntities).stream().filter(filter).findFirst(); 692 } 693 694 /** 695 * Extracts from a given token (that represents a file) all the attributes 696 * (variables) that are declared directly in a type (class) and not within a 697 * method etc. 698 * 699 * @param classEntities 700 * the {@link ShallowEntity} of the class that contains the method 701 * for which suggestions are to be generated 702 * @return A list containing all the names (identifiers) of the global variables 703 */ 704 public static List<String> getClassAttributes(List<ShallowEntity> classEntities) { 705 706 List<String> globalVariables = new ArrayList<>(); 707 708 for (ShallowEntity classEntity : classEntities) { 709 710 List<ShallowEntity> attributes = classEntity.getChildrenOfType(EShallowEntityType.ATTRIBUTE); 711 712 for (ShallowEntity attribute : attributes) { 713 globalVariables.add(attribute.getName()); 714 } 715 } 716 717 return globalVariables; 718 } 719 720 /** Checks if the entity is a loop (for, foreach, while). */ 721 public static boolean isLoop(ShallowEntity entity) { 722 return LOOP_SUBTYPES.contains(entity.getSubtype()); 723 } 724 725 /** 726 * Checks if the given entity is part of a branching statement but not the first 727 * option of the branches. Those statements must not be separated from the first 728 * part of the branching statement when we consider refactoring suggestions. 729 * 730 * @return For entities of {@link SubTypeNames subtype} ELSE IF, ELSE, CATCH, 731 * FINALLY, CASE, DEFAULT, USING, DO will return <code>true</code> but 732 * for other subtypes (including IF, SWITCH, TRY) return 733 * <code>false</code>. 734 */ 735 public static boolean isSubpartOfBranchingStatement(ShallowEntity entity) { 736 return SUBPART_BRANCHING_STATEMENTS.contains(entity.getSubtype()); 737 } 738 739 /** 740 * Checks whether the given method is a C++ constructor. 741 */ 742 public static boolean isCppConstructor(ELanguage language, ShallowEntity entity) { 743 return language.equals(ELanguage.CPP) && entity.getType().equals(EShallowEntityType.METHOD) 744 && SubTypeNames.CONSTRUCTOR.equals(entity.getSubtype()); 745 746 } 747 748 /** 749 * @return whether the given entity is a lambda method. 750 */ 751 public static boolean isLambdaMethod(ShallowEntity entity) { 752 return entity.getType() == EShallowEntityType.METHOD && entity.getSubtype() == SubTypeNames.LAMBDA; 753 } 754 755 /** 756 * Determines the language of the given entity or <code>null</code> if it cannot 757 * be determined 758 */ 759 public static ELanguage getLanguage(ShallowEntity entity) { 760 UnmodifiableList<IToken> tokens = entity.includedTokens(); 761 if (tokens.isEmpty()) { 762 return null; 763 } 764 return CollectionUtils.getAny(tokens).getLanguage(); 765 } 766}