diff --git a/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesIndex.java b/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesIndex.java index b3e94c465e70..cf4249cecd65 100644 --- a/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesIndex.java +++ b/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesIndex.java @@ -55,7 +55,7 @@ import java.util.Map; public class DuplicatesIndex extends FileBasedIndexExtension 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(); diff --git a/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesInspectionBase.java b/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesInspectionBase.java index f015fe21213b..f44a3e2953c7 100644 --- a/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesInspectionBase.java +++ b/platform/duplicates-analysis/src/com/intellij/dupLocator/index/DuplicatesInspectionBase.java @@ -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 descriptors = new SmartList(); + final Ref myProcessorRef = new Ref(); - final TreeMap reportedRanges = new TreeMap(); - final TIntObjectHashMap reportedFiles = new TIntObjectHashMap(); - final TIntObjectHashMap reportedPsi = new TIntObjectHashMap(); - 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 myProcessor; + @Override + public void process(final LighterAST ast, final LighterASTNode node, int hash) { + class LightDuplicatedCodeProcessor extends DuplicatedCodeProcessor { + + 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 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() { - 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 { - 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 map = reportedRanges.subMap(fragmentStartOffsetInteger, endOffset); - int newFragmentSize = !map.isEmpty() ? 0:1; + @Override + protected PsiElement getPsi(PsiFragment node) { + PsiElement[] elements = node.getElements(); - Iterator 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 descriptors = new SmartList(); + + if (processor != null) { + + for(Map.Entry 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 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 implements FileBasedIndex.ValueProcessor { + final TreeMap reportedRanges = new TreeMap(); + final TIntObjectHashMap reportedFiles = new TIntObjectHashMap(); + final TIntObjectHashMap reportedPsi = new TIntObjectHashMap(); + 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 map = reportedRanges.subMap(fragmentStartOffsetInteger, endOffset); + int newFragmentSize = !map.isEmpty() ? 0:1; + + Iterator 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); + } }