/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.codeInspection.expression; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.codeInspection.ex.BaseLocalInspectionTool; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.impl.DirectoryIndex; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.impl.PsiDocumentManagerImpl; import com.intellij.psi.scope.util.PsiScopesUtil; import com.intellij.util.Query; import gw.internal.gosu.parser.IGosuClassInternal; import gw.internal.gosu.parser.ThisSymbol; import gw.internal.gosu.parser.TypeLoaderAccess; import gw.internal.gosu.parser.TypeLord; import gw.internal.gosu.parser.statements.ClassStatement; import gw.lang.GosuShop; import gw.lang.parser.IExpression; import gw.lang.parser.IHasInnerClass; import gw.lang.parser.IParseIssue; import gw.lang.parser.IParseTree; import gw.lang.parser.IParsedElement; import gw.lang.parser.IScope; import gw.lang.parser.ISymbol; import gw.lang.parser.ISymbolTable; import gw.lang.parser.ITypeUsesMap; import gw.lang.parser.Keyword; import gw.lang.parser.expressions.IMemberAccessExpression; import gw.lang.parser.expressions.IMemberExpansionExpression; import gw.lang.parser.expressions.IMethodCallExpression; import gw.lang.parser.expressions.IStringLiteralExpression; import gw.lang.parser.expressions.ITypeLiteralExpression; import gw.lang.parser.resources.Res; import gw.lang.parser.statements.IClassStatement; import gw.lang.parser.statements.IFunctionStatement; import gw.lang.parser.statements.INamespaceStatement; import gw.lang.parser.statements.INoOpStatement; import gw.lang.parser.statements.IUsesStatement; import gw.lang.reflect.IEnumType; import gw.lang.reflect.IEnumValue; import gw.lang.reflect.IMetaType; import gw.lang.reflect.IMethodInfo; import gw.lang.reflect.INamespaceType; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IRelativeTypeInfo; import gw.lang.reflect.IType; import gw.lang.reflect.ITypeInfo; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.TypeName; import gw.lang.reflect.java.JavaTypes; import gw.lang.reflect.module.IModule; import gw.plugin.ij.intentions.CaseMismatchSimpleQuickFix; import gw.plugin.ij.lang.parser.GosuElementTypes; import gw.plugin.ij.lang.parser.GosuRawPsiElement; import gw.plugin.ij.lang.psi.api.IGosuPackageDefinition; import gw.plugin.ij.lang.psi.api.types.IGosuCodeReferenceElement; import gw.plugin.ij.lang.psi.impl.AbstractGosuClassFileImpl; import gw.plugin.ij.lang.psi.impl.GosuElementVisitor; import gw.plugin.ij.lang.psi.impl.GosuParserConfigurer; import gw.plugin.ij.lang.psi.impl.expressions.*; import gw.plugin.ij.lang.psi.impl.statements.typedef.members.GosuMethodImpl; import gw.plugin.ij.util.GosuBundle; import gw.plugin.ij.util.GosuModuleUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Set; public class GosuCaseMismatchReferenceInspection extends BaseLocalInspectionTool { @Nls @NotNull @Override public String getGroupDisplayName() { return GosuBundle.message( "inspection.group.name.case.mismatch.issues" ); } @Nls @NotNull @Override public String getDisplayName() { return GosuBundle.message( "inspection.case.mismatch.reference.plain" ); } @Override public boolean isEnabledByDefault() { // Must turn this on explicitly, too much of an impact on performance for regular use return false; } @NotNull @Override public PsiElementVisitor buildVisitor( @NotNull final ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session ) { return new GosuElementVisitor() { @Override public void visitElement( PsiElement element ) { if( element instanceof GosuRawPsiElement ) { IParsedElement pe = ((GosuRawPsiElement)element).getParsedElement(); if( isDoNotVerify( pe ) ) { return; } if( pe instanceof INoOpStatement ) { IParseTree loc = pe.getLocation(); if( loc != null ) { List<IParseTree> children = loc.getChildren(); if( children.size() == 0 ) { String wrongName = loc.getTextFromTokens(); if( wrongName != null && !wrongName.isEmpty() ) { handleIdentifierOrMethodCall( element, element.getFirstChild(), pe ); } } } } } else if( element instanceof GosuIdentifierImpl ) { // Special handling for property get/set mismatch... PsiElement parent = element.getParent(); if( parent instanceof GosuMethodImpl ) { IFunctionStatement pe = ((GosuMethodImpl)parent).getParsedElement(); if( pe != null && pe.hasParseException( Res.MSG_EXPECTING_PROPERTY_GET_OR_SET_MODIFIER ) ) { if( isPrecededByPropertyKeyword( element ) ) { String correctName = findCorrectCaseKeyword( element.getText() ); if( correctName != null ) { holder.registerProblem( element, GosuBundle.message( "inspection.case.mismatch.reference", "Keyword: " + correctName ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( element, correctName ) ); } } } } // Special handling for top level class/enum with wrong keyword case else if( parent instanceof GosuRawPsiElement ) { IParsedElement pe = ((GosuRawPsiElement)parent).getParsedElement(); if (pe instanceof INoOpStatement && pe.getParent() instanceof ClassStatement ) { String correctName = findCorrectCaseKeyword( element.getText() ); if( correctName != null ) { holder.registerProblem( element, GosuBundle.message( "inspection.case.mismatch.reference", "Keyword: " + correctName ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( element, correctName ) ); } } } } } private boolean isPrecededByPropertyKeyword( PsiElement element ) { element = element.getPrevSibling(); while( element instanceof PsiWhiteSpace ) { element = element.getPrevSibling(); } return element != null && element.getText().equalsIgnoreCase( Keyword.KW_property.getName() ); } private boolean isDoNotVerify( IParsedElement pe ) { if( pe == null ) { return false; } IGosuClass gsClass = pe.getGosuClass(); if( gsClass == null ) { return false; } IModule module = gsClass.getTypeLoader().getModule(); if( module == null ) { return false; } TypeSystem.pushModule( module ); try { IType outer = TypeLord.getOuterMostEnclosingClass( gsClass ); IType type = TypeSystem.getByFullNameIfValid( "gw.testharness.DoNotVerifyResource" ); return type != null && outer.getTypeInfo().hasAnnotation( type ); } finally { TypeSystem.popModule( module ); } } @Override public void visitStringLiteral( GosuStringLiteralImpl expr ) { IExpression pe = expr.getParsedElement(); if( pe == null ) { return; } if( isDoNotVerify( pe ) ) { return; } // Fix typekey string literal case mismatch if( pe instanceof IStringLiteralExpression && pe.hasImmediateParseIssue( Res.MSG_VALUE_MISMATCH ) ) { IType type = pe.getImmediateParseIssue( Res.MSG_VALUE_MISMATCH ).getExpectedType(); if( type instanceof IEnumType ) { String wrongName = expr.getText(); String quote = String.valueOf( wrongName.charAt( 0 ) ); if( wrongName.startsWith( """ ) ) { quote = """; } for( IEnumValue value : ((IEnumType)type).getEnumValues() ) { String strCorrectCase = quote + value.getCode() + quote; if( equalsDifferentCase( strCorrectCase, wrongName ) ) { holder.registerProblem( expr, GosuBundle.message( "inspection.case.mismatch.reference", "Enum constant:" + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( expr, strCorrectCase ) ); } } } } } @Override public void visitIdentifierExpression( GosuIdentifierExpressionImpl expr ) { IParsedElement pe = expr.getParsedElement(); if( pe == null ) { return; } if( !pe.hasParseExceptions() ) { return; } if( isDoNotVerify( pe ) ) { return; } handleIdentifierOrMethodCall( expr, expr.getReferenceNameElement(), pe ); } @Override public void visitMethodCallExpression( GosuMethodCallExpressionImpl expr ) { IMethodCallExpression pe = expr.getParsedElement(); if( pe == null ) { return; } if( !pe.hasImmediateParseIssue( Res.MSG_NO_SUCH_FUNCTION ) ) { return; } if( isDoNotVerify( pe ) ) { return; } handleIdentifierOrMethodCall( expr, expr.getReferenceNameElement(), pe ); } @Override public void visitPackageDefinition( IGosuPackageDefinition expr ) { INamespaceStatement stmt = (INamespaceStatement)expr.getParsedElement(); if( stmt == null ) { return; } if( isDoNotVerify( stmt ) ) { return; } IGosuClass gsClass = stmt.getGosuClass(); if( gsClass == null ) { return; } IClassStatement classStmt = gsClass.getClassStatement(); if( !classStmt.hasImmediateParseIssue( Res.MSG_WRONG_NAMESPACE ) && !classStmt.hasImmediateParseIssue( Res.MSG_WRONG_CLASSNAME ) ) { return; } IGosuCodeReferenceElement packageExpr = expr.getPackageReference(); if( packageExpr == null ) { return; } String strCorrectCase = gsClass.getNamespace(); if( strCorrectCase != null && !strCorrectCase.equals( packageExpr.getText() ) && strCorrectCase.equalsIgnoreCase( packageExpr.getText() ) ) { holder.registerProblem( packageExpr, GosuBundle.message( "inspection.case.mismatch.reference", "Package :" + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( packageExpr, strCorrectCase ) ); } } private void handleIdentifierOrMethodCall( PsiElement elem, PsiElement refElem, IParsedElement pe ) { if( refElem == null ) { return; } if( pe == null ) { return; } IGosuClassInternal gsClass = (IGosuClassInternal)pe.getGosuClass(); if( gsClass == null ) { return; } if( isNoOpOnSameLineAsExistingProblem( refElem ) ) { // Avoid trying to fix multiple "unexpecte token" related problems, // chances are the first such error needs to be fixed, but not subsequent ones return; } IModule module = gsClass.getTypeLoader().getModule(); if( module == null ) { return; } TypeSystem.pushModule( module ); try { String caseKind = "Symbol"; String wrongName = refElem.getText(); String strCorrectCase = findCorrectSymbolFromSymTableCase( elem, gsClass, wrongName ); if( strCorrectCase == null ) { caseKind = "Relative type"; strCorrectCase = findCorrectRelativeTypeCase( elem, (IGosuClassInternal)pe.getGosuClass(), wrongName ); if( strCorrectCase == null ) { caseKind = "Enum constant"; strCorrectCase = findCorrectEnumConstCase( pe, wrongName ); if( strCorrectCase == null ) { caseKind = "Keyword"; strCorrectCase = findCorrectCaseKeyword( wrongName ); } } } if( strCorrectCase != null ) { holder.registerProblem( refElem, GosuBundle.message( "inspection.case.mismatch.reference", caseKind + ":" + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( refElem, strCorrectCase ) ); } } finally { TypeSystem.popModule( module ); } } private boolean isNoOpOnSameLineAsExistingProblem( PsiElement refElem ) { if( !(refElem instanceof GosuIdentifierImpl) || refElem.getParent().getNode().getElementType() != GosuElementTypes.ELEM_TYPE_NoOpStatement ) { return false; } int lineNumber = findLineNumber( refElem ); boolean bRet = false; for( ProblemDescriptor p : holder.getResults() ) { holder.registerProblem( p ); if( !bRet && p.getPsiElement().getContainingFile() == refElem.getContainingFile() && p.getLineNumber() == lineNumber ) { bRet = true; } } return bRet; } private int findLineNumber( PsiElement psi ) { final Document document = PsiDocumentManagerImpl.getInstance( psi.getProject() ).getDocument( psi.getContainingFile() ); return document.getLineNumber( psi.getTextOffset() ) + 1; } private String findLocalVarCorrectCase( PsiElement expr, String wrongName ) { FindLocalVarInScope localFinder = new FindLocalVarInScope( wrongName ); PsiScopesUtil.treeWalkUp( localFinder, expr, null ); PsiLocalVariable localFound = localFinder.getLocalFound(); if( localFound != null ) { String text = localFound.getNameIdentifier().getText(); if( equalsDifferentCase( text, wrongName ) ) { return text; } } return null; } @Override public void visitTypeLiteral( GosuTypeLiteralImpl expr ) { ITypeLiteralExpression pe = expr.getParsedElement(); if( pe == null ) { return; } if( !pe.hasImmediateParseIssue( Res.MSG_INVALID_TYPE ) && !pe.hasImmediateParseIssue( Res.MSG_TYPE_MISMATCH ) ) { return; } if( isDoNotVerify( pe ) ) { return; } IModule module = GosuModuleUtil.findModuleForPsiElement( expr ); if( module == null ) { return; } TypeSystem.pushModule( module ); try { PsiElement referenceNameElement = expr.getReferenceNameElement(); if( referenceNameElement == null ) { return; } String wrongName = referenceNameElement.getText(); if( pe.hasImmediateParseIssue( Res.MSG_INVALID_TYPE ) ) { String caseKind = "Type literal"; String strCorrectCase = findCorrectRelativeTypeCase( expr, (IGosuClassInternal)pe.getGosuClass(), wrongName ); if( strCorrectCase == null ) { caseKind = "Keyword"; strCorrectCase = findCorrectCaseKeyword( wrongName ); } if( strCorrectCase != null ) { holder.registerProblem(referenceNameElement, GosuBundle.message( "inspection.case.mismatch.reference", caseKind + ": " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix(referenceNameElement, strCorrectCase ) ); } } else { String strCorrectCase = findCorrectSymbolFromSymTableCase(expr, (IGosuClassInternal) pe.getGosuClass(), referenceNameElement.getText()); if( strCorrectCase != null ) { holder.registerProblem(referenceNameElement, GosuBundle.message( "inspection.case.mismatch.reference", "Symbol: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix(referenceNameElement, strCorrectCase ) ); } } } finally { TypeSystem.popModule( module ); } } @Override public void visitFieldAccessExpression(GosuFieldAccessExpressionImpl expr) { IMemberAccessExpression pe = expr.getParsedElement(); if( pe == null ) { return; } IParseIssue noPropErr = pe.getImmediateParseIssue( Res.MSG_NO_PROPERTY_DESCRIPTOR_FOUND ); IParseIssue noTypeErr = pe.getImmediateParseIssue( Res.MSG_NO_TYPE_ON_NAMESPACE ); if( noPropErr == null && noTypeErr == null ) { return; } if( isDoNotVerify( pe ) ) { return; } IModule module = GosuModuleUtil.findModuleForPsiElement( expr ); if( module == null ) { return; } TypeSystem.pushModule( module ); try { String strCorrectCase = noPropErr != null ? findCorrectPropertyCase( pe, pe.getMemberName() ) : findCorrectTypeCase( pe, pe.getMemberName() ); if( strCorrectCase != null ) { holder.registerProblem( expr.getReferenceNameElement(), GosuBundle.message( "inspection.case.mismatch.reference", "Member reference: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( expr.getReferenceNameElement(), strCorrectCase ) ); } else if( noTypeErr != null && expr.getQualifier() != null ) { // It may be that a qualifier is mistaken for a namespace e.g,. contact.TaxID s/b Contact.TaxID strCorrectCase = findCorrectSymbolFromSymTableCase( expr.getQualifier(), (IGosuClassInternal)pe.getGosuClass(), expr.getQualifier().getText() ); if( strCorrectCase != null ) { holder.registerProblem( expr.getQualifier(), GosuBundle.message( "inspection.case.mismatch.reference", "Symbol: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( expr.getQualifier(), strCorrectCase ) ); } } else if( noPropErr != null && noPropErr.getPlainMessage().contains( Keyword.KW_static.getName() ) ) { // It may be that a "no static property" error is really masking a "no inner class" error... strCorrectCase = findCorrectTypeCase( pe, pe.getMemberName() ); if( strCorrectCase != null ) { holder.registerProblem( expr.getReferenceNameElement(), GosuBundle.message( "inspection.case.mismatch.reference", "Inner class: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( expr.getReferenceNameElement(), strCorrectCase ) ); } else { // Fix case where improperly cased identifier is confused for relative type e.g., Claim.someNonstaticMethod() s/b claim.someNonstaticMethod() PsiExpression refElem = expr.getQualifier(); strCorrectCase = findCorrectSymbolFromSymTableCase( refElem, (IGosuClassInternal)pe.getGosuClass(), expr.getQualifier().getText() ); if( strCorrectCase != null ) { holder.registerProblem( refElem, GosuBundle.message( "inspection.case.mismatch.reference", "Symbol reference: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( refElem, strCorrectCase ) ); } } } } finally { TypeSystem.popModule( module ); } } @Override public void visitMemberExpansionExpression( GosuMemberExpansionExpressionImpl expr ) { IMemberExpansionExpression pe = expr.getParsedElement(); if( pe == null ) { return; } if( !pe.hasImmediateParseIssue( Res.MSG_NO_PROPERTY_DESCRIPTOR_FOUND ) ) { return; } if( isDoNotVerify( pe ) ) { return; } IModule module = GosuModuleUtil.findModuleForPsiElement( expr ); if( module == null ) { return; } TypeSystem.pushModule( module ); try { String strCorrectCase = findCorrectPropertyCase( pe, pe.getMemberName() ); if( strCorrectCase != null ) { holder.registerProblem( expr.getReferenceNameElement(), GosuBundle.message( "inspection.case.mismatch.reference", "Member expansion: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( expr.getReferenceNameElement(), strCorrectCase ) ); } } finally { TypeSystem.popModule( module ); } } @Override public void visitBeanMethodCallExpression( GosuBeanMethodCallExpressionImpl expr ) { IMemberAccessExpression pe = expr.getParsedElement(); if( pe == null ) { return; } PsiElement refElem = expr.getReferenceNameElement(); if( refElem == null ) { return; } IModule module = GosuModuleUtil.findModuleForPsiElement( expr ); if( module == null ) { return; } if( isDoNotVerify( pe ) ) { return; } TypeSystem.pushModule( module ); try { String strCorrectCase = null; if( pe.hasImmediateParseIssue( Res.MSG_NO_METHOD_DESCRIPTOR_FOUND_FOR_METHOD ) ) { strCorrectCase = findCorrectMethodCase( pe, refElem.getText() ); if( strCorrectCase != null ) { holder.registerProblem( expr, GosuBundle.message( "inspection.case.mismatch.reference", "Method reference: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( refElem, strCorrectCase ) ); } } if( strCorrectCase == null ) { if( pe.hasImmediateParseIssue( Res.MSG_METHOD_IS_NOT_STATIC ) || pe.hasImmediateParseIssue( Res.MSG_NO_METHOD_DESCRIPTOR_FOUND_FOR_METHOD ) ) { // Fix case where improperly cased identifier is confused for relative type e.g., Claim.someNonstaticMethod() s/b claim.someNonstaticMethod() refElem = expr.getQualifier(); if( refElem != null ) { strCorrectCase = findCorrectSymbolFromSymTableCase( refElem, (IGosuClassInternal)pe.getGosuClass(), refElem.getText() ); if( strCorrectCase != null ) { holder.registerProblem( refElem, GosuBundle.message( "inspection.case.mismatch.reference", "Symbol reference: " + strCorrectCase ), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CaseMismatchFix( refElem, strCorrectCase ) ); } } } } } finally { TypeSystem.popModule( module ); } } private String findCorrectPropertyCase( IMemberAccessExpression pe, String wrongName ) { ITypeInfo rootTypeInfo = pe instanceof IMemberExpansionExpression ? pe.getRootType().getComponentType().getTypeInfo() : pe.getRootType().getTypeInfo(); List<? extends IPropertyInfo> props = rootTypeInfo instanceof IRelativeTypeInfo ? ((IRelativeTypeInfo)rootTypeInfo).getProperties( pe.getGosuClass() ) : rootTypeInfo.getProperties(); for( IPropertyInfo pi : props ) { if( equalsDifferentCase( pi.getName(), wrongName ) ) { return pi.getName(); } } return null; } private String findCorrectTypeCase( IMemberAccessExpression pe, String wrongName ) { IType rootType = pe.getRootType(); if( rootType instanceof INamespaceType ) { for( TypeName tn : ((INamespaceType)rootType).getChildren( pe.getGosuClass() ) ) { String fullName = tn.name.replace( '$', '.' ); String name = StringUtil.getShortName( fullName ); if( equalsDifferentCase( wrongName, name ) ) { return name; } } } else if( rootType instanceof IMetaType ) { IType type = TypeLord.getPureGenericType( ((IMetaType)rootType).getType() ); if( type instanceof IHasInnerClass ) { for( IType inner : ((IHasInnerClass)type).getInnerClasses() ) { String relativeName = StringUtil.getShortName( inner.getRelativeName() ); if( equalsDifferentCase( wrongName, relativeName ) ) { return relativeName; } } } } return null; } private boolean equalsDifferentCase( String name1, String name2 ) { return name2 != null && name1 != null && !name2.equals( name1 ) && name2.equalsIgnoreCase( name1 ); } private String findCorrectMethodCase( IMemberAccessExpression pe, String wrongName ) { // Note, this doesn't take into account overloading. But the worst that can happen // is that we replace a "method not found" error with a "illegal arg" error. Too // few of those to make a difference. ITypeInfo rootTypeInfo = pe.getRootType().getTypeInfo(); List<? extends IMethodInfo> methods = rootTypeInfo instanceof IRelativeTypeInfo ? ((IRelativeTypeInfo)rootTypeInfo).getMethods( pe.getGosuClass() ) : rootTypeInfo.getMethods(); for( IMethodInfo mi : methods ) { if( equalsDifferentCase( mi.getDisplayName(), wrongName ) ) { return mi.getDisplayName(); } } return null; } private String findCorrectCaseKeyword( String text ) { for( String kw : Keyword.getAll() ) { if( equalsDifferentCase( kw, text ) ) { return kw; } } return null; } private String findCorrectSymbolFromSymTableCase( PsiElement elem, IGosuClassInternal gsClass, String wrongName ) { String correctName = findLocalVarCorrectCase( elem, wrongName ); if( correctName != null ) { return correctName; } ISymbolTable symTable = GosuParserConfigurer.getSymbolTable( (AbstractGosuClassFileImpl)elem.getContainingFile() ); symTable = symTable == null ? GosuShop.createSymbolTable() : symTable; IScope scope = symTable.pushScope(); try { gsClass.putClassMembers( null, symTable, gsClass, true ); gsClass.putClassMembers( null, symTable, gsClass, false ); symTable.putSymbol( new ThisSymbol( JavaTypes.OBJECT(), symTable ) ); putFunctionParameters( symTable, elem ); for( Object sym : symTable.getSymbols().values() ) { String displayName = ((ISymbol)sym).getDisplayName(); if( sym != null && equalsDifferentCase( displayName, wrongName ) ) { return displayName; } } return null; } finally { symTable.popScope( scope ); } } private void putFunctionParameters( ISymbolTable symTable, PsiElement elem ) { PsiElement csr = elem; while( csr != null && !(csr instanceof GosuMethodImpl) ) { csr = csr.getParent(); } if( csr != null ) { IFunctionStatement pe = ((GosuMethodImpl)csr).getParsedElement(); if( pe != null && pe.getDynamicFunctionSymbol() != null ) { List<ISymbol> args = pe.getDynamicFunctionSymbol().getArgs(); for( ISymbol sym : args ) { symTable.putSymbol( sym ); } } } } private String findCorrectEnumConstCase( IParsedElement pe, String wrongName ) { if( pe.getParseExceptions().isEmpty() ) { return null; } IType expectedType = pe.getParseExceptions().get( 0 ).getExpectedType(); if( expectedType != null ) { expectedType = TypeLord.getCoreType( expectedType ); if( expectedType.isEnum() ) { for( String value : ((IEnumType)expectedType).getEnumConstants() ) { if( equalsDifferentCase( value, wrongName ) ) { return value; } } } } return null; } private String findCorrectRelativeTypeCase( PsiElement elem, IGosuClassInternal gsClass, String wrongName ) { if( wrongName == null || wrongName.isEmpty() ) { return null; } String correctName = invertCapitalization( wrongName ); if( _findCorrectRelativeTypeCase( elem, gsClass, correctName ) ) { return correctName; } String lowercase = wrongName.toLowerCase(); if( !lowercase.equals( wrongName ) ) { if( _findCorrectRelativeTypeCase( elem, gsClass, lowercase ) ) { return lowercase; } else { return _findCorrectRelativeTypeCaseInCurrentPackage(elem, gsClass, lowercase); } } return null; } private String _findCorrectRelativeTypeCaseInCurrentPackage(PsiElement elem, IGosuClassInternal gsClass, String wrongName) { ITypeUsesMap typeUses = ((IGosuClassInternal) TypeLord.getOuterMostEnclosingClass(gsClass)).getTypeUsesMap(); IModule mod = GosuModuleUtil.findModuleForPsiElement(elem); if (mod == null || typeUses == null) { return null; } Set<IUsesStatement> usesStatements = typeUses.getUsesStatements(); for (IUsesStatement usesStmt : usesStatements) { String typeName = getTypeName(usesStmt.getTypeName()); if (typeName.toLowerCase().equals(wrongName)) { return typeName; } } Set<String> namespaces = typeUses.getNamespaces(); for (String namespace : namespaces) { String namespaceNoDot = maybeRemoveTrailingDot(namespace); if (!namespaceNoDot.equals("")) { INamespaceType nameSpace = TypeSystem.getNamespace(namespaceNoDot, mod); if (nameSpace != null) { Set<TypeName> typeNames = nameSpace.getChildren(null); for (TypeName tn : typeNames) { String tnLow = tn.name.toLowerCase(); if (tnLow.equals(namespace.toLowerCase() + wrongName)) { return getTypeName(tn.name); } } } } } return null; } private String getTypeName(String name) { int b = name.lastIndexOf("."); if(b == -1) { return null; } return name.substring(b + 1); } private String maybeRemoveTrailingDot(String s) { String ret = s; if (s.endsWith(".")) { ret = s.substring(0, s.length() - 1); } return ret; } private boolean _findCorrectRelativeTypeCase( PsiElement elem, IGosuClassInternal gsClass, String correctName ) { // Is this a relative type name? ITypeUsesMap typeUses = ((IGosuClassInternal)TypeLord.getOuterMostEnclosingClass( gsClass )).getTypeUsesMap(); IType type = TypeLoaderAccess.instance().getTypeByRelativeNameIfValid_NoGenerics( correctName, typeUses ); if( type != null ) { return true; } else { // Is this a root package name? IModule mod = GosuModuleUtil.findModuleForPsiElement( elem ); if( mod != null ) { Query<VirtualFile> dirs = DirectoryIndex.getInstance( elem.getProject() ).getDirectoriesByPackageName( correctName, true ); if( !dirs.findAll().isEmpty() ) { return true; } } } return false; } private String invertCapitalization( String wrongName ) { if( wrongName.length() == 0 ) { return null; } StringBuilder correctName = new StringBuilder( wrongName ); if( Character.isLowerCase( wrongName.charAt( 0 ) ) ) { correctName.replace( 0, 1, String.valueOf( Character.toUpperCase( wrongName.charAt( 0 ) ) ) ); } else { correctName.replace( 0, 1, String.valueOf( Character.toLowerCase( wrongName.charAt( 0 ) ) ) ); } return correctName.toString(); } }; } private class CaseMismatchFix implements LocalQuickFix { private final CaseMismatchSimpleQuickFix _quickFix; public CaseMismatchFix( PsiElement id, String correctName ) { _quickFix = new CaseMismatchSimpleQuickFix( id, correctName ); } @NotNull public String getName() { return _quickFix.getText(); } @NotNull public String getFamilyName() { return GosuBundle.message( "inspection.group.name.case.mismatch.issues" ); } public void applyFix( @NotNull Project project, @NotNull ProblemDescriptor descriptor ) { PsiElement element = descriptor.getPsiElement(); if( element == null ) { return; } final PsiFile psiFile = element.getContainingFile(); if( _quickFix.isAvailable( project, null, psiFile ) ) { _quickFix.invoke( project, null, psiFile ); } } } }