mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
java duplicates uses light tree processing
This commit is contained in:
@@ -55,7 +55,7 @@ import java.util.Map;
|
||||
public class DuplicatesIndex extends FileBasedIndexExtension<Integer, TIntArrayList> implements PsiDependentIndex {
|
||||
static boolean ourEnabled = SystemProperties.getBooleanProperty("idea.enable.duplicates.online.calculation",
|
||||
isEnabledByDefault());
|
||||
static boolean ourEnabledLightProfiles = false;
|
||||
static boolean ourEnabledLightProfiles = true;
|
||||
|
||||
private static boolean isEnabledByDefault() {
|
||||
Application application = ApplicationManager.getApplication();
|
||||
|
||||
+176
-67
@@ -3,10 +3,16 @@ package com.intellij.dupLocator.index;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.dupLocator.DuplicatesProfile;
|
||||
import com.intellij.dupLocator.DuplocatorState;
|
||||
import com.intellij.dupLocator.LightDuplicateProfile;
|
||||
import com.intellij.dupLocator.treeHash.FragmentsCollector;
|
||||
import com.intellij.dupLocator.util.PsiFragment;
|
||||
import com.intellij.lang.LighterAST;
|
||||
import com.intellij.lang.LighterASTNode;
|
||||
import com.intellij.lang.TreeBackedLighterAST;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectFileIndex;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileWithId;
|
||||
@@ -35,94 +41,126 @@ public class DuplicatesInspectionBase extends LocalInspectionTool {
|
||||
final DuplicatesProfile profile = DuplicatesIndex.findDuplicatesProfile(psiFile.getFileType());
|
||||
if (profile == null) return ProblemDescriptor.EMPTY_ARRAY;
|
||||
|
||||
final DuplocatorState state = profile.getDuplocatorState(psiFile.getLanguage());
|
||||
final SmartList<ProblemDescriptor> descriptors = new SmartList<ProblemDescriptor>();
|
||||
final Ref<DuplicatedCodeProcessor> myProcessorRef = new Ref<DuplicatedCodeProcessor>();
|
||||
|
||||
final TreeMap<Integer, TextRange> reportedRanges = new TreeMap<Integer, TextRange>();
|
||||
final TIntObjectHashMap<VirtualFile> reportedFiles = new TIntObjectHashMap<VirtualFile>();
|
||||
final TIntObjectHashMap<PsiElement> reportedPsi = new TIntObjectHashMap<PsiElement>();
|
||||
final TIntIntHashMap reportedOffsetInOtherFiles = new TIntIntHashMap();
|
||||
final TIntIntHashMap fragmentSize = new TIntIntHashMap();
|
||||
if (profile instanceof LightDuplicateProfile && DuplicatesIndex.ourEnabledLightProfiles) {
|
||||
|
||||
profile.createVisitor(new FragmentsCollector() {
|
||||
@Override
|
||||
public void add(int hash, final int cost, @Nullable final PsiFragment frag) {
|
||||
if (!DuplicatesIndex.isIndexedFragment(frag, cost, profile, state)) {
|
||||
return;
|
||||
((LightDuplicateProfile)profile).process(psiFile, new LightDuplicateProfile.Callback() {
|
||||
DuplicatedCodeProcessor<LighterASTNode> myProcessor;
|
||||
@Override
|
||||
public void process(final LighterAST ast, final LighterASTNode node, int hash) {
|
||||
class LightDuplicatedCodeProcessor extends DuplicatedCodeProcessor<LighterASTNode> {
|
||||
|
||||
LightDuplicatedCodeProcessor(VirtualFile file, Project project) {
|
||||
super(file, project);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TextRange getRangeInElement(LighterASTNode node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PsiElement getPsi(LighterASTNode node) {
|
||||
return ((TreeBackedLighterAST)ast).unwrap(node).getPsi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getStartOffset(LighterASTNode node) {
|
||||
return node.getStartOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getEndOffset(LighterASTNode node) {
|
||||
return node.getEndOffset();
|
||||
}
|
||||
}
|
||||
if (myProcessor == null) {
|
||||
myProcessor = new LightDuplicatedCodeProcessor(virtualFile, psiFile.getProject());
|
||||
myProcessorRef.set(myProcessor);
|
||||
}
|
||||
myProcessor.process(hash, node);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final DuplocatorState state = profile.getDuplocatorState(psiFile.getLanguage());
|
||||
profile.createVisitor(new FragmentsCollector() {
|
||||
DuplicatedCodeProcessor<PsiFragment> myProcessor;
|
||||
@Override
|
||||
public void add(int hash, final int cost, @Nullable final PsiFragment frag) {
|
||||
if (!DuplicatesIndex.isIndexedFragment(frag, cost, profile, state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressManager.checkCanceled();
|
||||
FileBasedIndex.getInstance().processValues(DuplicatesIndex.NAME, hash, null, new FileBasedIndex.ValueProcessor<TIntArrayList>() {
|
||||
final ProjectFileIndex myProjectFileIndex = ProjectFileIndex.SERVICE.getInstance(psiFile.getProject());
|
||||
@Override
|
||||
public boolean process(final VirtualFile file, final TIntArrayList list) {
|
||||
for(int i = 0, len = list.size(); i < len; ++i) {
|
||||
ProgressManager.checkCanceled();
|
||||
class OldDuplicatedCodeProcessor extends DuplicatedCodeProcessor<PsiFragment> {
|
||||
|
||||
int value = list.getQuick(i);
|
||||
OldDuplicatedCodeProcessor(VirtualFile file, Project project) {
|
||||
super(file, project);
|
||||
}
|
||||
|
||||
if (myProjectFileIndex.isInSource(virtualFile) && !myProjectFileIndex.isInSource(file)) return true;
|
||||
if (!myProjectFileIndex.isInSource(virtualFile) && myProjectFileIndex.isInSource(file)) return true;
|
||||
final int startOffset = frag.getStartOffset();
|
||||
final int endOffset = frag.getEndOffset();
|
||||
if (file.equals(virtualFile) && value >= startOffset && value < endOffset) continue;
|
||||
|
||||
PsiElement[] elements = frag.getElements();
|
||||
PsiElement target = elements[0];
|
||||
@Override
|
||||
protected TextRange getRangeInElement(PsiFragment node) {
|
||||
PsiElement[] elements = node.getElements();
|
||||
TextRange rangeInElement = null;
|
||||
if (elements.length > 1) {
|
||||
PsiElement firstElement = elements[0];
|
||||
target = firstElement.getParent();
|
||||
|
||||
PsiElement lastElement = elements[elements.length - 1];
|
||||
rangeInElement = new TextRange(
|
||||
elements[0].getStartOffsetInParent(),
|
||||
lastElement.getStartOffsetInParent() + lastElement.getTextLength()
|
||||
);
|
||||
}
|
||||
return rangeInElement;
|
||||
}
|
||||
|
||||
Integer fragmentStartOffsetInteger = startOffset;
|
||||
SortedMap<Integer,TextRange> map = reportedRanges.subMap(fragmentStartOffsetInteger, endOffset);
|
||||
int newFragmentSize = !map.isEmpty() ? 0:1;
|
||||
@Override
|
||||
protected PsiElement getPsi(PsiFragment node) {
|
||||
PsiElement[] elements = node.getElements();
|
||||
|
||||
Iterator<Integer> iterator = map.keySet().iterator();
|
||||
while(iterator.hasNext()) {
|
||||
Integer next = iterator.next();
|
||||
iterator.remove();
|
||||
reportedFiles.remove(next);
|
||||
reportedOffsetInOtherFiles.remove(next);
|
||||
reportedPsi.remove(next);
|
||||
newFragmentSize += fragmentSize.remove(next);
|
||||
}
|
||||
return elements.length > 1 ? elements[0].getParent() : elements[0];
|
||||
}
|
||||
|
||||
reportedRanges.put(fragmentStartOffsetInteger, rangeInElement);
|
||||
reportedFiles.put(fragmentStartOffsetInteger, file);
|
||||
reportedOffsetInOtherFiles.put(fragmentStartOffsetInteger, value);
|
||||
reportedPsi.put(fragmentStartOffsetInteger, target);
|
||||
fragmentSize.put(fragmentStartOffsetInteger, newFragmentSize);
|
||||
@Override
|
||||
protected int getStartOffset(PsiFragment node) {
|
||||
return node.getStartOffset();
|
||||
}
|
||||
|
||||
return false;
|
||||
@Override
|
||||
protected int getEndOffset(PsiFragment node) {
|
||||
return node.getEndOffset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, GlobalSearchScope.projectScope(psiFile.getProject()));
|
||||
if (myProcessor == null) {
|
||||
myProcessor = new OldDuplicatedCodeProcessor(virtualFile, psiFile.getProject());
|
||||
myProcessorRef.set(myProcessor);
|
||||
}
|
||||
myProcessor.process(hash, frag);
|
||||
}
|
||||
}, true).visitNode(psiFile);
|
||||
}
|
||||
|
||||
DuplicatedCodeProcessor<?> processor = myProcessorRef.get();
|
||||
|
||||
final SmartList<ProblemDescriptor> descriptors = new SmartList<ProblemDescriptor>();
|
||||
|
||||
if (processor != null) {
|
||||
|
||||
for(Map.Entry<Integer, TextRange> entry:processor.reportedRanges.entrySet()) {
|
||||
final Integer offset = entry.getKey();
|
||||
// todo 3 statements constant
|
||||
if (processor.fragmentSize.get(offset) < 3) continue;
|
||||
final VirtualFile file = processor.reportedFiles.get(offset);
|
||||
String message = "Found duplicated code in " + file.getPath();
|
||||
|
||||
PsiElement targetElement = processor.reportedPsi.get(offset);
|
||||
TextRange rangeInElement = entry.getValue();
|
||||
final int offsetInOtherFile = processor.reportedOffsetInOtherFiles.get(offset);
|
||||
|
||||
LocalQuickFix fix = createNavigateToDupeFix(file, offsetInOtherFile);
|
||||
ProblemDescriptor descriptor = manager
|
||||
.createProblemDescriptor(targetElement, rangeInElement, message, ProblemHighlightType.WEAK_WARNING, isOnTheFly, fix);
|
||||
descriptors.add(descriptor);
|
||||
}
|
||||
}, true).visitNode(psiFile);
|
||||
|
||||
for(Map.Entry<Integer, TextRange> entry:reportedRanges.entrySet()) {
|
||||
final Integer offset = entry.getKey();
|
||||
// todo 3 statements constant
|
||||
if (fragmentSize.get(offset) < 3) continue;
|
||||
final VirtualFile file = reportedFiles.get(offset);
|
||||
String message = "Found duplicated code in " + file.getPath();
|
||||
|
||||
PsiElement targetElement = reportedPsi.get(offset);
|
||||
TextRange rangeInElement = entry.getValue();
|
||||
final int offsetInOtherFile = reportedOffsetInOtherFiles.get(offset);
|
||||
|
||||
LocalQuickFix fix = createNavigateToDupeFix(file, offsetInOtherFile);
|
||||
ProblemDescriptor descriptor = manager
|
||||
.createProblemDescriptor(targetElement, rangeInElement, message, ProblemHighlightType.WEAK_WARNING, isOnTheFly, fix);
|
||||
descriptors.add(descriptor);
|
||||
}
|
||||
|
||||
return descriptors.isEmpty() ? null : descriptors.toArray(new ProblemDescriptor[descriptors.size()]);
|
||||
@@ -131,4 +169,75 @@ public class DuplicatesInspectionBase extends LocalInspectionTool {
|
||||
protected LocalQuickFix createNavigateToDupeFix(@NotNull VirtualFile file, int offsetInOtherFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
static abstract class DuplicatedCodeProcessor<T> implements FileBasedIndex.ValueProcessor<TIntArrayList> {
|
||||
final TreeMap<Integer, TextRange> reportedRanges = new TreeMap<Integer, TextRange>();
|
||||
final TIntObjectHashMap<VirtualFile> reportedFiles = new TIntObjectHashMap<VirtualFile>();
|
||||
final TIntObjectHashMap<PsiElement> reportedPsi = new TIntObjectHashMap<PsiElement>();
|
||||
final TIntIntHashMap reportedOffsetInOtherFiles = new TIntIntHashMap();
|
||||
final TIntIntHashMap fragmentSize = new TIntIntHashMap();
|
||||
final VirtualFile virtualFile;
|
||||
final Project project;
|
||||
final ProjectFileIndex myProjectFileIndex;
|
||||
T myNode;
|
||||
|
||||
DuplicatedCodeProcessor(VirtualFile file, Project project) {
|
||||
virtualFile = file;
|
||||
this.project = project;
|
||||
myProjectFileIndex = ProjectFileIndex.SERVICE.getInstance(project);
|
||||
}
|
||||
|
||||
void process(int hash, T node) {
|
||||
ProgressManager.checkCanceled();
|
||||
myNode = node;
|
||||
FileBasedIndex.getInstance().processValues(DuplicatesIndex.NAME, hash, null, this, GlobalSearchScope.projectScope(project));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(VirtualFile file, TIntArrayList list) {
|
||||
for(int i = 0, len = list.size(); i < len; ++i) {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
int value = list.getQuick(i);
|
||||
|
||||
if (myProjectFileIndex.isInSource(virtualFile) && !myProjectFileIndex.isInSource(file)) return true;
|
||||
if (!myProjectFileIndex.isInSource(virtualFile) && myProjectFileIndex.isInSource(file)) return true;
|
||||
final int startOffset = getStartOffset(myNode);
|
||||
final int endOffset = getEndOffset(myNode);
|
||||
if (file.equals(virtualFile) && value >= startOffset && value < endOffset) continue;
|
||||
|
||||
PsiElement target = getPsi(myNode);
|
||||
TextRange rangeInElement = getRangeInElement(myNode);
|
||||
|
||||
Integer fragmentStartOffsetInteger = startOffset;
|
||||
SortedMap<Integer,TextRange> map = reportedRanges.subMap(fragmentStartOffsetInteger, endOffset);
|
||||
int newFragmentSize = !map.isEmpty() ? 0:1;
|
||||
|
||||
Iterator<Integer> iterator = map.keySet().iterator();
|
||||
while(iterator.hasNext()) {
|
||||
Integer next = iterator.next();
|
||||
iterator.remove();
|
||||
reportedFiles.remove(next);
|
||||
reportedOffsetInOtherFiles.remove(next);
|
||||
reportedPsi.remove(next);
|
||||
newFragmentSize += fragmentSize.remove(next);
|
||||
}
|
||||
|
||||
reportedRanges.put(fragmentStartOffsetInteger, rangeInElement);
|
||||
reportedFiles.put(fragmentStartOffsetInteger, file);
|
||||
reportedOffsetInOtherFiles.put(fragmentStartOffsetInteger, value);
|
||||
reportedPsi.put(fragmentStartOffsetInteger, target);
|
||||
fragmentSize.put(fragmentStartOffsetInteger, newFragmentSize);
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract TextRange getRangeInElement(T node);
|
||||
protected abstract PsiElement getPsi(T node);
|
||||
|
||||
protected abstract int getStartOffset(T node);
|
||||
protected abstract int getEndOffset(T node);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user