PsiElementVisitor (@NotNull ProblemsHolder holder, boolean isOnTheFly) { return new JavaElementVisitor() { @Override public void visitCodeBlock(@NotNull PsiCodeBlock block) { if (block.getTextLength() < minLength) return; PsiParameterListOwner container = PsiTreeUtil.getParentOfType(block, PsiParameterListOwner.class); if (container == null) return; PsiElement body = container.getBody(); if (body == null) return; PsiStatement[] statements = block.getStatements(); BitSet declarations = getDeclarations(statements); if (declarations.isEmpty()) return; int maxLength = body.getTextLength() * 3 / 5; if (maxLength < minLength) return; int maxCount; if (block == body) { maxCount = statements.length - 1; if (ArrayUtil.getLastElement(statements) instanceof PsiReturnStatement) { maxCount--; } } else { maxCount = statements.length; } PsiElement fragment = ControlFlowUtil.findCodeFragment(block); for (int count = maxCount; count > 1; count--) { for (int from = 0; from < statements.length - count; from++) { int to = from + count; // Check that we have at least one declaration int nextDeclaration = declarations.nextSetBit(from); if (nextDeclaration == -1 || nextDeclaration >= to) continue; PsiStatement[] range = Arrays.copyOfRange(statements, from, to); if (ContainerUtil.exists(range, e -> e instanceof PsiSwitchLabelStatementBase)) continue; TextRange textRange = getRange(range); if (textRange.getLength() < minLength || textRange.getLength() > maxLength) continue; try { ControlFlowWrapper wrapper = new ControlFlowWrapper(fragment, range); Collection<PsiStatement> exitStatements = wrapper.prepareExitStatements(range, fragment); if (!exitStatements.isEmpty()) continue; if (wrapper.isGenerateConditionalExit() || wrapper.isReturnPresentBetween()) continue; PsiVariable[] variables = wrapper.getOutputVariables(); if (variables.length != 1) continue; PsiVariable output = variables[0]; if (SideEffectsVisitor.hasSideEffectOrSimilarUseOutside(range, output)) continue; List<PsiVariable> inputVariables = wrapper.getInputVariables(fragment, range, variables); if (inputVariables.size() > maxParameters) continue; ExtractMethodAnalyzerKt.findExtractOptions(Arrays.asList(range)); // check whether ExtractException will happen if (voidPrefix(fragment, range, output)) continue; if (!outputUsedInLastStatement(range, output)) continue; if (to < statements.length) { PsiStatement nextStatement = statements[to]; if (nextStatement instanceof PsiReturnStatement ret && ExpressionUtils.isReferenceTo(ret.getReturnValue(), output)) { textRange = textRange.union(ret.getTextRangeInParent()); } } List<LocalQuickFix> fixes = new ArrayList<>(); fixes.add(new ExtractMethodFix(from, count, output, inputVariables)); if (inputVariables.size() > 1) { fixes.add(LocalQuickFix.from(new UpdateInspectionOptionFix( ExtractMethodRecommenderInspection.this, "maxParameters", JavaAnalysisBundle.message("inspection.extract.method.dont.suggest.parameters", inputVariables.size()), inputVariables.size() - 1))); } if (textRange.getLength() < 10_000) { fixes.add(LocalQuickFix.from(new UpdateInspectionOptionFix( ExtractMethodRecommenderInspection.this, "minLength", JavaAnalysisBundle.message("inspection.extract.method.dont.suggest.length"), textRange.getLength() + 1))); } int firstLineBreak = textRange.substring(block.getText()).indexOf('\n'); if (firstLineBreak > -1) { textRange = TextRange.from(textRange.getStartOffset(), firstLineBreak); } holder.registerProblem(block, JavaAnalysisBundle.message("inspection.extract.method.message", output.getName()), ProblemHighlightType.WEAK_WARNING, textRange, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); return; } catch (PrepareFailedException | ControlFlowWrapper.ExitStatementsNotSameException | ExtractException ignored) { } } } } private static boolean outputUsedInLastStatement(PsiStatement[] range, PsiVariable output) { int outDecl = Arrays.asList(range).indexOf(output.getParent()); return outDecl >= range.length - 1 || VariableAccessUtils.variableIsUsed(output, range[range.length - 1]); } @NotNull private static BitSet getDeclarations(PsiStatement[] statements) { BitSet declarations = new BitSet(); for (int i = 0; i < statements.length; i++) { if (statements[i] instanceof PsiDeclarationStatement decl && ArrayUtil.getFirstElement(decl.getDeclaredElements()) instanceof PsiLocalVariable) { declarations.set(i); } } return declarations; } private static boolean voidPrefix(@NotNull PsiElement fragment, @NotNull PsiStatement @NotNull [] range, @NotNull PsiVariable output) throws PrepareFailedException, ControlFlowWrapper.ExitStatementsNotSameException { if (output.getParent() instanceof PsiDeclarationStatement statement) { int declarationIndex = Arrays.asList(range).indexOf(statement); if (declarationIndex > 0) { PsiStatement[] subRange = Arrays.copyOf(range, declarationIndex); ControlFlowWrapper subWrapper = new ControlFlowWrapper(fragment, subRange); subWrapper.prepareExitStatements(subRange, fragment); return subWrapper.getOutputVariables().length == 0; } } return false; } @NotNull private static TextRange getRange(PsiStatement[] statements) { PsiElement start = statements[0]; while (true) { PsiElement prev = PsiTreeUtil.skipWhitespacesBackward(start); if (prev instanceof PsiComment) { start = prev; } else { break; } } int startOffset = start.getTextRangeInParent().getStartOffset(); int endOffset = statements[statements.length - 1].getTextRangeInParent().getEndOffset(); return TextRange.create(startOffset, endOffset); } }; }