package com.chrisfolger.needsmoredojo.intellij.inspections;
import com.chrisfolger.needsmoredojo.core.amd.define.DefineResolver;
import com.chrisfolger.needsmoredojo.core.amd.importing.InvalidDefineException;
import com.chrisfolger.needsmoredojo.core.amd.naming.MismatchedImportsDetector;
import com.chrisfolger.needsmoredojo.core.amd.naming.MismatchedImportsDetectorCache;
import com.chrisfolger.needsmoredojo.core.amd.naming.NameResolver;
import com.chrisfolger.needsmoredojo.core.settings.DojoSettings;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.lang.javascript.psi.JSCallExpression;
import com.intellij.lang.javascript.psi.JSLiteralExpression;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class MismatchedImportsInspection extends DojoInspection
{
@Override
public String getDisplayName()
{
return "Check for inconsistently named imports";
}
@Override
public boolean isEnabledByDefault()
{
return true;
}
@Override
public String getShortName()
{
return "MismatchedImportsInspection";
}
@Nullable
@Override
public String getStaticDescription() {
return "Detects AMD imports that have inconsistent naming between the module path and the variable name. " +
"\n\nExample: \n\ndefine([\n 'dojo/foo'\n], function(someOtherName) {}); \n\nvs\n\n define([\n 'dojo/foo'\n'], function(foo) {});";
}
@Nls
@NotNull
@Override
public String getGroupDisplayName()
{
return "Needs More Dojo";
}
@NotNull
@Override
public String[] getGroupPath()
{
return new String[] { "JavaScript", "Needs More Dojo "};
}
private void addProblemsForBlock(JSCallExpression expression, List<ProblemDescriptor> descriptors, PsiFile file, InspectionManager manager)
{
List<PsiElement> blockDefines = new ArrayList<PsiElement>();
List<PsiElement> blockParameters = new ArrayList<PsiElement>();
try {
new DefineResolver().addDefinesAndParametersOfImportBlock(expression, blockDefines, blockParameters);
} catch (InvalidDefineException e) {
}
Map<String, Integer> parameterOccurrences = new HashMap<String, Integer>();
for(PsiElement parameter : blockParameters)
{
if(parameter == null) continue;
if(parameterOccurrences.containsKey(parameter.getText()))
{
parameterOccurrences.put(parameter.getText(), parameterOccurrences.get(parameter.getText()) + 1);
}
else
{
parameterOccurrences.put(parameter.getText(), 1);
}
}
LocalQuickFix noFix = null;
List<MismatchedImportsDetector.Mismatch> mismatches = new MismatchedImportsDetector().matchOnList(blockDefines.toArray(new PsiElement[0]),
blockParameters.toArray(new PsiElement[0]),
ServiceManager.getService(file.getProject(), DojoSettings.class).getNamingExceptionList(),
ServiceManager.getService(file.getProject(), DojoSettings.class),
ServiceManager.getService(file.getProject(), MismatchedImportsDetectorCache.class));
for(int i=0;i<mismatches.size();i++)
{
MismatchedImportsDetector.Mismatch mismatch = mismatches.get(i);
PsiElement define = mismatch.getDefine();
PsiElement parameter = mismatch.getParameter();
if(define != null && parameter != null && !(define instanceof JSLiteralExpression))
{
// this is to account for expressions in the define/require array literal. They are perfectly valid,
// so we can't flag them as mismatched
continue;
}
String defineString = "<no string>";
String parameterString = "<no string>";
if(define != null)
{
defineString = define.getText();
}
if(parameter != null)
{
parameterString = parameter.getText();
}
LocalQuickFix fix = noFix;
LocalQuickFix exceptionFix = null;
if(define != null && parameter != null)
{
String normalName = NameResolver.defineToParameter(define.getText(), ServiceManager.getService(define.getProject(), DojoSettings.class).getNamingExceptionList());
if(parameterOccurrences.containsKey(normalName))
{
fix = new MismatchedImportsQuickFix(define, parameter, mismatch.getAbsolutePath());
}
else
{
fix = new MismatchedImportsQuickFix(define, parameter, null);
}
exceptionFix = new AddExceptionQuickFix(define, parameter, mismatch.getAbsolutePath());
}
// check if the previous import was also mismatched. If it was, it's possible that they were flipped by accident.
// but exclude the case where there are three or more in a row, because then it's probably just that the two
// lists are completely out of sync.
boolean importsSwapped = false;
if(i > 0)
{
boolean nextIsMismatched = false;
if(i <= mismatches.size() - 2)
{
MismatchedImportsDetector.Mismatch nextMismatch = mismatches.get(i+1);
if(nextMismatch.getIndex() == mismatch.getIndex() + 1)
{
nextIsMismatched = true;
}
}
MismatchedImportsDetector.Mismatch previousMismatch = mismatches.get(i-1);
if(previousMismatch.getIndex() == mismatch.getIndex() - 1 && !nextIsMismatched)
{
importsSwapped = true;
}
}
if (parameter != null)
{
descriptors.add(manager.createProblemDescriptor(parameter, String.format("Mismatch between define %s and parameter %s", defineString, parameterString), true, ProblemHighlightType.ERROR, true, fix, exceptionFix));
}
if (define != null)
{
descriptors.add(manager.createProblemDescriptor(define, String.format("Mismatch between define %s and parameter %s", defineString, parameterString), true, ProblemHighlightType.ERROR, true, fix, exceptionFix));
}
if(importsSwapped)
{
SwapImportsQuickFix importFix = new SwapImportsQuickFix(mismatch, mismatches.get(i-1));
descriptors.addAll(addQuickFixToOtherMismatch(mismatch, mismatches.get(i - 1), importFix, manager));
descriptors.addAll(addQuickFixToOtherMismatch(mismatches.get(i - 1), mismatch, importFix, manager));
}
}
}
@Override
public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull final InspectionManager manager, boolean isOnTheFly)
{
if(!isEnabled(file.getProject()))
{
return new ProblemDescriptor[0];
}
DefineResolver resolver = new DefineResolver();
final List<ProblemDescriptor> descriptors = new ArrayList<ProblemDescriptor>();
Set<JSCallExpression> expressions = resolver.getAllImportBlocks(file);
for(JSCallExpression expression : expressions)
{
addProblemsForBlock(expression, descriptors, file, manager);
}
return descriptors.toArray(new ProblemDescriptor[0]);
}
private List<ProblemDescriptor> addQuickFixToOtherMismatch(MismatchedImportsDetector.Mismatch mismatch, MismatchedImportsDetector.Mismatch secondMismatch, SwapImportsQuickFix quickFix, InspectionManager manager)
{
List<ProblemDescriptor> descriptors = new ArrayList<ProblemDescriptor>();
if(mismatch.getDefine() == null || mismatch.getParameter() == null)
{
return descriptors;
}
descriptors.add(manager.createProblemDescriptor(mismatch.getDefine(), String.format("Potentially swapped imports: %s and %s", mismatch.getDefine().getText(), secondMismatch.getDefine().getText()), true, ProblemHighlightType.ERROR, true, quickFix));
descriptors.add(manager.createProblemDescriptor(mismatch.getParameter(), String.format("Potentially swapped imports: %s and %s", mismatch.getDefine().getText(), secondMismatch.getDefine().getText()), true, ProblemHighlightType.ERROR, true, quickFix));
return descriptors;
}
}