package com.intellij.lang.javascript.inspections.actionscript; import com.intellij.codeInsight.daemon.impl.quickfix.RenameElementFix; import com.intellij.codeInsight.daemon.impl.quickfix.RenameFileFix; import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.javascript.flex.mxml.FlexCommonTypeNames; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.ASTNode; import com.intellij.lang.annotation.Annotation; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.javascript.*; import com.intellij.lang.javascript.findUsages.JSReadWriteAccessDetector; import com.intellij.lang.javascript.flex.*; import com.intellij.lang.javascript.highlighting.JSFixFactory; import com.intellij.lang.javascript.highlighting.JSSemanticHighlightingUtil; import com.intellij.lang.javascript.index.JSSymbolUtil; import com.intellij.lang.javascript.index.JSTypeEvaluateManager; import com.intellij.lang.javascript.inspections.actionscript.fixes.ActionScriptConstructorChecker; import com.intellij.lang.javascript.psi.*; import com.intellij.lang.javascript.psi.e4x.JSE4XFilterQueryArgumentList; import com.intellij.lang.javascript.psi.e4x.JSE4XNamespaceReference; import com.intellij.lang.javascript.psi.ecmal4.*; import com.intellij.lang.javascript.psi.ecmal4.impl.JSAttributeImpl; import com.intellij.lang.javascript.psi.ecmal4.impl.JSAttributeListImpl; import com.intellij.lang.javascript.psi.ecmal4.impl.JSPackageStatementImpl; import com.intellij.lang.javascript.psi.ecmal4.impl.JSPackageWrapper; import com.intellij.lang.javascript.psi.impl.JSReferenceExpressionImpl; import com.intellij.lang.javascript.psi.resolve.*; import com.intellij.lang.javascript.psi.types.*; import com.intellij.lang.javascript.psi.types.primitives.JSStringType; import com.intellij.lang.javascript.psi.types.primitives.JSVoidType; import com.intellij.lang.javascript.refactoring.changeSignature.JSMethodDescriptor; import com.intellij.lang.javascript.ui.JSFormatUtil; import com.intellij.lang.javascript.validation.*; import com.intellij.lang.javascript.validation.fixes.*; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlTagChild; import com.intellij.psi.xml.XmlText; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.XmlAttributeDescriptor; import com.intellij.xml.XmlElementDescriptor; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; import java.util.*; import static com.intellij.lang.javascript.psi.JSCommonTypeNames.BOOLEAN_CLASS_NAME; import static com.intellij.lang.javascript.psi.JSCommonTypeNames.NUMBER_CLASS_NAME; /** * @author Konstantin.Ulitin */ public class ActionScriptAnnotatingVisitor extends TypedJSAnnotatingVisitor { private static final String[] EXTENSIONS_TO_CHECK = { JavaScriptSupportLoader.ECMA_SCRIPT_L4_FILE_EXTENSION, JavaScriptSupportLoader.ECMA_SCRIPT_L4_FILE_EXTENSION2, JavaScriptSupportLoader.ECMA_SCRIPT_L4_FILE_EXTENSION3, JavaScriptSupportLoader.MXML_FILE_EXTENSION, JavaScriptSupportLoader.FXG_FILE_EXTENSION }; public ActionScriptAnnotatingVisitor(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) { super(psiElement, holder); } @NotNull @Override protected ActionScriptConstructorChecker createConstructorChecker() { return new ActionScriptConstructorChecker(myProblemReporter); } protected static SignatureMatchResult checkCompatibleSignature(final JSFunction fun, final JSFunction override) { JSParameterList nodeParameterList = fun.getParameterList(); JSParameterList overrideParameterList = override.getParameterList(); final JSParameter[] parameters = nodeParameterList != null ? nodeParameterList.getParameterVariables() : JSParameter.EMPTY_ARRAY; final JSParameter[] overrideParameters = overrideParameterList != null ? overrideParameterList.getParameterVariables() : JSParameter.EMPTY_ARRAY; SignatureMatchResult result = parameters.length != overrideParameters.length ? SignatureMatchResult.PARAMETERS_DIFFERS : SignatureMatchResult.COMPATIBLE_SIGNATURE; if (result == SignatureMatchResult.COMPATIBLE_SIGNATURE) { for (int i = 0; i < parameters.length; ++i) { if (!compatibleType(overrideParameters[i].getTypeString(), parameters[i].getTypeString(), overrideParameterList, nodeParameterList) || overrideParameters[i].hasInitializer() != parameters[i].hasInitializer() ) { result = SignatureMatchResult.PARAMETERS_DIFFERS; break; } } } if (result == SignatureMatchResult.COMPATIBLE_SIGNATURE) { if (!compatibleType(override.getReturnTypeString(), fun.getReturnTypeString(), override, fun)) { result = SignatureMatchResult.RETURN_TYPE_DIFFERS; } } if (result == SignatureMatchResult.COMPATIBLE_SIGNATURE) { if (override.getKind() != fun.getKind()) result = SignatureMatchResult.FUNCTION_KIND_DIFFERS; } return result; } /** * @deprecated use {@link com.intellij.lang.javascript.psi.JSTypeUtils * #areTypesCompatible(com.intellij.lang.javascript.psi.JSType, com.intellij.lang.javascript.psi.JSType)} instead. */ protected static boolean compatibleType(String overrideParameterType, String parameterType, PsiElement overrideContext, PsiElement funContext) { // TODO: This should be more accurate if (overrideParameterType != null && !overrideParameterType.equals(parameterType)) { parameterType = JSImportHandlingUtil.resolveTypeName(parameterType, funContext); overrideParameterType = JSImportHandlingUtil.resolveTypeName(overrideParameterType, overrideContext); if (!overrideParameterType.equals(parameterType)) { if (parameterType != null && // TODO: getter / setter to have the same types (JSTypeEvaluateManager.isArrayType(overrideParameterType) && JSTypeEvaluateManager.getBaseArrayType(overrideParameterType).equals(parameterType) || JSTypeEvaluateManager.isArrayType(parameterType) || JSTypeEvaluateManager.getBaseArrayType(parameterType).equals(overrideParameterType)) ) { return true; } return false; } return true; } else if (overrideParameterType == null && parameterType != null && !"*".equals(parameterType)) { return false; } return true; } @NotNull @Override protected JSAnnotatorProblemReporter createProblemReporter(PsiElement context) { return new JSAnnotatorProblemReporter(myHolder) { @Nullable @Override protected String getAnnotatorInspectionId() { return null; } }; } @NotNull @Override protected JSTypeChecker<Annotation> createTypeChecker(PsiElement context) { return new ActionScriptTypeChecker(myProblemReporter); } @NotNull @Override protected JSFunctionSignatureChecker createFunctionSignatureChecker(PsiElement context) { return new ActionScriptFunctionSignatureChecker(myTypeChecker, myProblemReporter); } public static void checkFileUnderSourceRoot(final JSNamedElement aClass, ErrorReportingClient client) { PsiElement nameIdentifier = aClass.getNameIdentifier(); if (nameIdentifier == null) { nameIdentifier = aClass.getFirstChild(); } final PsiFile containingFile = aClass.getContainingFile(); final VirtualFile file = containingFile.getVirtualFile(); if (file == null) return; final VirtualFile rootForFile = ProjectRootManager.getInstance(containingFile.getProject()).getFileIndex().getSourceRootForFile(file); if (rootForFile == null) { client.reportError(nameIdentifier.getNode(), JSBundle.message("javascript.validation.message.file.should.be.under.source.root"), ErrorReportingClient.ProblemKind.WARNING); } if (!(aClass instanceof JSPackageStatement)) { VirtualFile parent = file.getParent(); if (parent == null) return; // EA-90191 boolean found = false; for (String ext : EXTENSIONS_TO_CHECK) { String name = file.getNameWithoutExtension() + "." + ext; VirtualFile child = parent.findChild(name); if (child != null && name.equals(child.getName())) { // check for case-insensitive filesystems if (found) { client.reportError(nameIdentifier.getNode(), JSBundle.message("javascript.validation.message.more.than.one.named.object.in.package"), ErrorReportingClient.ProblemKind.ERROR); break; } else { found = true; } } } } } protected static ChangeSignatureFix createChangeBaseMethodSignatureFix(final JSFunction superMethod, final JSFunction override) { JSType type = override.getReturnType(); String s = StringUtil.notNullize(type != null ? type.getResolvedTypeText() : null); ChangeSignatureFix fix = new ChangeSignatureFix(superMethod, JSMethodDescriptor.getParameters(superMethod)); fix.setOverriddenReturnType(s); return fix; } @Override public void visitJSAttributeNameValuePair(@NotNull final JSAttributeNameValuePair attributeNameValuePair) { final boolean ok = checkReferences(attributeNameValuePair); if (!ok) return; // check if attribute value must be FQN of a class class inherited from some other class if (attributeNameValuePair.getValueNode() == null) return; final PsiElement parent = attributeNameValuePair.getParent(); final XmlElementDescriptor descriptor = parent instanceof JSAttributeImpl ? ((JSAttributeImpl)parent).getBackedDescriptor() : null; final String attributeName = StringUtil.notNullize(attributeNameValuePair.getName(), JSAttributeNameValuePair.DEFAULT); final XmlAttributeDescriptor attributeDescriptor = descriptor == null ? null : descriptor.getAttributeDescriptor(attributeName, null); final String baseClassFqns = attributeDescriptor == null ? null : attributeDescriptor.getDefaultValue(); if (baseClassFqns != null && !StringUtil.isEmptyOrSpaces(baseClassFqns)) { final PsiReference[] references = attributeNameValuePair.getReferences(); PsiReference lastReference = references.length > 0 ? references[0] : null; for (final PsiReference reference : references) { if (reference.getRangeInElement().getEndOffset() > lastReference.getRangeInElement().getEndOffset()) { lastReference = reference; } } final PsiElement resolved = lastReference != null ? lastReference.resolve() : null; if (resolved instanceof JSClass) { boolean correctClass = false; final Collection<String> resolvedBaseClasses = new ArrayList<>(); final GlobalSearchScope scope = JSResolveUtil.getResolveScope(attributeNameValuePair); for (String baseClassFqn : StringUtil.split(baseClassFqns, ",")) { if ("Object".equals(baseClassFqn)) { correctClass = true; break; } final PsiElement baseClass = ActionScriptClassResolver.findClassByQNameStatic(baseClassFqn, attributeNameValuePair); if (baseClass instanceof JSClass) { resolvedBaseClasses.add(baseClassFqn); if (JSInheritanceUtil.isParentClass((JSClass)resolved, (JSClass)baseClass, false, scope)) { correctClass = true; break; } } } if (!correctClass) { final String classesForMessage = resolvedBaseClasses.isEmpty() ? StringUtil.replace(baseClassFqns, ",", ", ") : StringUtil.join(resolvedBaseClasses, ", "); myHolder.createErrorAnnotation(calcRangeForReferences(lastReference), JSBundle.message("javascript.expected.class.or.descendant", classesForMessage)); } } else if (resolved != attributeNameValuePair) { // for some reason int and uint are resolved to self-reference JSResolveUtil.MyResolveResult() instead of usual JSClass myHolder.createErrorAnnotation(attributeNameValuePair.getValueNode(), JSBundle.message("javascript.qualified.class.name.expected")); } } } @Override public void visitJSIncludeDirective(@NotNull final JSIncludeDirective includeDirective) { checkReferences(includeDirective); } @Override protected void checkImplementedMethods(JSClass jsClass, ErrorReportingClient reportingClient) { checkActionScriptImplementedMethods(jsClass, reportingClient); } public static void checkActionScriptImplementedMethods(final JSClass jsClass, final ErrorReportingClient reportingClient) { final JSCollectMembersToImplementProcessor implementedMethodProcessor = new JSImplementedMethodProcessor(jsClass) { ImplementMethodsFix implementMethodsFix = null; protected void addNonimplementedFunction(final JSFunction function) { final ASTNode node = myJsClass.findNameIdentifier(); if (node == null) return; if (implementMethodsFix == null) implementMethodsFix = new ImplementMethodsFix(myJsClass); implementMethodsFix.addElementToProcess(function); String messageId = function.isGetProperty() ? "javascript.validation.message.interface.method.not.implemented2" : function.isSetProperty() ? "javascript.validation.message.interface.method.not.implemented3" : "javascript.validation.message.interface.method.not.implemented"; String message = JSBundle.message(messageId, function.getName(), ((JSClass)JSResolveUtil.findParent(function)).getQualifiedName()); reportingClient.reportError(node, message, ErrorReportingClient.ProblemKind.ERROR, implementMethodsFix); } protected void addImplementedFunction(final JSFunction interfaceFunction, final JSFunction implementationFunction) { final JSAttributeList attributeList = implementationFunction.getAttributeList(); if (attributeList == null || attributeList.getAccessType() != JSAttributeList.AccessType.PUBLIC) { final ASTNode node = findElementForAccessModifierError(implementationFunction, attributeList); reportingClient.reportError(node, JSBundle.message("javascript.validation.message.interface.method.invalid.access.modifier"), ErrorReportingClient.ProblemKind.ERROR, JSFixFactory.getInstance().createChangeVisibilityFix(implementationFunction, JSAttributeList.AccessType.PUBLIC, null) ); } final SignatureMatchResult incompatibleSignature = checkCompatibleSignature(implementationFunction, interfaceFunction); if (incompatibleSignature != SignatureMatchResult.COMPATIBLE_SIGNATURE) { PsiElement parent = JSResolveUtil.findParent(implementationFunction); if (parent instanceof JSFile) { parent = JSResolveUtil.getClassReferenceForXmlFromContext(parent); } if (parent != myJsClass) { // some parent incorrectly implements method from our interface addNonimplementedFunction(interfaceFunction); return; } if (incompatibleSignature == SignatureMatchResult.PARAMETERS_DIFFERS) { final JSParameterList parameterList = implementationFunction.getParameterList(); final JSParameterList expectedParameterList = interfaceFunction.getParameterList(); ChangeSignatureFix changeSignatureFix = new ChangeSignatureFix(interfaceFunction, parameterList, true); reportingClient.reportError(parameterList.getNode(), JSBundle.message( "javascript.validation.message.interface.method.invalid.signature", expectedParameterList != null ? expectedParameterList.getText() : "()" ), ErrorReportingClient.ProblemKind.ERROR, new ChangeSignatureFix(implementationFunction, expectedParameterList, false) { @NotNull public String getText() { return JSBundle.message("javascript.fix.message.change.parameters.to.expected"); } }, changeSignatureFix); } else if (incompatibleSignature == SignatureMatchResult.RETURN_TYPE_DIFFERS) { PsiElement implementationReturnTypeExpr = implementationFunction.getReturnTypeElement(); JSType type = interfaceFunction.getReturnType(); final String interfaceReturnType = type != null ? type.getResolvedTypeText() : null; String msg = JSBundle .message("javascript.validation.message.interface.method.invalid.signature2", StringUtil.notNullize(interfaceReturnType)); reportingClient.reportError( implementationReturnTypeExpr != null ? implementationReturnTypeExpr.getNode() : implementationFunction.findNameIdentifier(), msg, ErrorReportingClient.ProblemKind.ERROR, new ChangeTypeFix(implementationFunction, interfaceReturnType, "javascript.fix.message.change.return.type.to.expected"), createChangeBaseMethodSignatureFix(interfaceFunction, implementationFunction)); } else if (incompatibleSignature == SignatureMatchResult.FUNCTION_KIND_DIFFERS) { String msg = JSBundle.message("javascript.validation.message.interface.method.invalid.signature3", interfaceFunction.getKind()); reportingClient.reportError( implementationFunction.findNameIdentifier(), msg, ErrorReportingClient.ProblemKind.ERROR); // TODO: fix } } } }; JSResolveUtil.processInterfaceMembers(jsClass, implementedMethodProcessor); } @Override protected void checkFunctionDeclaration(@NotNull final JSFunction node) { super.checkFunctionDeclaration(node); final ASTNode nameIdentifier = node.findNameIdentifier(); if (nameIdentifier == null) return; PsiElement parent = node.getParent(); if (parent instanceof JSFile) { parent = JSResolveUtil.getClassReferenceForXmlFromContext(parent); final String name = node.getName(); if (parent instanceof JSClass && name != null && name.equals(((JSClass)parent).getName()) && !isNative(node) && JavaScriptSupportLoader.isFlexMxmFile(parent.getContainingFile())) { final Annotation annotation = myHolder.createErrorAnnotation( nameIdentifier, JSBundle.message("javascript.validation.message.constructor.in.mxml.is.not.allowed") ); annotation.registerFix(new RemoveASTNodeFix("javascript.fix.remove.constructor", node.getNode())); } } if (parent instanceof JSPackageStatement) { checkNamedObjectIsInCorrespondingFile(node); } if (parent instanceof JSClass && !node.isConstructor()) { final JSAttributeList attributeList = node.getAttributeList(); final JSClass clazz = (JSClass)parent; if (attributeList == null || !attributeList.hasModifier(JSAttributeList.ModifierType.STATIC) && (attributeList.getAccessType() != JSAttributeList.AccessType.PRIVATE || attributeList.hasModifier(JSAttributeList.ModifierType.OVERRIDE) )) { final String qName = clazz.getQualifiedName(); final boolean hasOverride = attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.OVERRIDE); final Ref<JSFunction> set = new Ref<>(); boolean b = JSResolveUtil.iterateType(node, parent, qName, new JSOverrideHandler() { public boolean process(@NotNull final List<JSPsiElementBase> elements, final PsiElement scope, final String className) { //noinspection StringEquality if (qName == className || qName != null && qName.equals(className)) return true; JSFunction value = (JSFunction)elements.iterator().next(); set.set(value); DialectOptionHolder holder; if ("Object".equals(className)) { if (hasOverride && !attributeList.hasModifier(JSAttributeList.ModifierType.NATIVE)) { /*native modifier is written always*/ final ASTNode astNode = attributeList.getNode().findChildByType(JSTokenTypes.OVERRIDE_KEYWORD); final Annotation annotation = myHolder.createErrorAnnotation(astNode, JSBundle.message( "javascript.validation.message.function.override.for.object.method")); annotation.registerFix( new RemoveASTNodeFix("javascript.fix.remove.override.modifier", astNode) ); } return false; } else if (!hasOverride && (holder = myHighlighter.getDialectOptionsHolder()) != null && holder.isECMA4) { final Annotation annotation = myHolder.createErrorAnnotation(nameIdentifier, JSBundle.message( "javascript.validation.message.function.override.without.override.modifier", className)); annotation.registerFix(new AddOverrideIntentionAction(node)); } else { JSAttributeList attrList = value.getAttributeList(); JSAttributeList parentAttrList = ((JSAttributeListOwner)scope).getAttributeList(); if (attrList != null && attrList.hasModifier(JSAttributeList.ModifierType.FINAL) || parentAttrList != null && parentAttrList.hasModifier(JSAttributeList.ModifierType.FINAL) ) { myHolder.createErrorAnnotation( attributeList.getNode().findChildByType(JSTokenTypes.OVERRIDE_KEYWORD), JSBundle.message("javascript.validation.message.can.not.override.final.method", className) ); } } if (clazz.isInterface()) { myHolder.createErrorAnnotation(nameIdentifier, JSBundle.message( "javascript.validation.message.function.override.for.interface", className)); } return false; } }, true); if (b && hasOverride) { final ASTNode astNode = attributeList.getNode().findChildByType(JSTokenTypes.OVERRIDE_KEYWORD); final Annotation annotation = myHolder.createErrorAnnotation(astNode, JSBundle.message( "javascript.validation.message.function.override.without.parent.method")); annotation.registerFix( new RemoveASTNodeFix("javascript.fix.remove.override.modifier", astNode) ); } if (!b && hasOverride) { final JSFunction override = set.get(); final JSAttributeList overrideAttrList = override.getAttributeList(); String overrideNs = null; if (attributeList.getAccessType() != overrideAttrList.getAccessType() || (overrideNs = JSResolveUtil.getNamespaceValue(overrideAttrList)) != null && !overrideNs.equals(JSResolveUtil.getNamespaceValue(attributeList))) { String newVisibility; IntentionAction fix; if (overrideNs != null) { newVisibility = overrideNs; fix = JSFixFactory.getInstance().createChangeVisibilityFix(node, null ,overrideNs); } else { newVisibility = JSFormatUtil.formatVisibility(overrideAttrList.getAccessType()); fix = JSFixFactory.getInstance().createChangeVisibilityFix(node, overrideAttrList.getAccessType() ,null); } final Annotation annotation = myHolder.createErrorAnnotation( findElementForAccessModifierError(node, attributeList), JSBundle.message("javascript.validation.message.function.override.incompatible.access.modifier", newVisibility)); annotation.registerFix(fix); } final SignatureMatchResult incompatibleSignature = checkCompatibleSignature(node, override); if (incompatibleSignature == SignatureMatchResult.PARAMETERS_DIFFERS) { final JSParameterList nodeParameterList = node.getParameterList(); final JSParameterList overrideParameterList = override.getParameterList(); final Annotation annotation = myHolder.createErrorAnnotation( nodeParameterList != null ? nodeParameterList.getNode() : node.findNameIdentifier(), JSBundle.message("javascript.validation.message.function.override.incompatible.signature", overrideParameterList != null ? overrideParameterList.getText() : "()" ) ); annotation.registerFix(new ChangeSignatureFix(node, overrideParameterList, false) { @NotNull public String getText() { return JSBundle.message("javascript.fix.message.change.parameters.to.expected"); } }); annotation.registerFix(new ChangeSignatureFix(override, nodeParameterList, true)); } else if (incompatibleSignature == SignatureMatchResult.RETURN_TYPE_DIFFERS) { PsiElement returnTypeExpr = node.getReturnTypeElement(); JSType type = override.getReturnType(); final String baseReturnType = type != null ? type.getResolvedTypeText() : null; String msg = JSBundle .message("javascript.validation.message.function.override.incompatible.signature2", StringUtil.notNullize(baseReturnType)); final Annotation annotation = myHolder.createErrorAnnotation(returnTypeExpr != null ? returnTypeExpr.getNode() : node.findNameIdentifier(), msg); annotation.registerFix(new ChangeTypeFix(node, baseReturnType, "javascript.fix.message.change.return.type.to.expected")); annotation.registerFix(createChangeBaseMethodSignatureFix(override, node)); } else if (incompatibleSignature == SignatureMatchResult.FUNCTION_KIND_DIFFERS) { String msg = JSBundle .message("javascript.validation.message.function.override.incompatible.signature3", override.getKind().toString()); final Annotation annotation = myHolder.createErrorAnnotation(node.findNameIdentifier(), msg); //annotation.registerFix(); } } } else if (attributeList.hasModifier(JSAttributeList.ModifierType.STATIC)) { if (clazz.isInterface()) { reportStaticMethodProblem(attributeList, "javascript.validation.message.static.method.in.interface"); } if (attributeList.hasModifier(JSAttributeList.ModifierType.OVERRIDE)) { reportStaticMethodProblem(attributeList, "javascript.validation.message.static.method.with.override"); } } } } private static boolean isNative(final JSFunction function) { final JSAttributeList attributeList = function.getAttributeList(); return attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.NATIVE); } private void reportStaticMethodProblem(JSAttributeList attributeList, String key) { final ASTNode astNode = attributeList.getNode().findChildByType(JSTokenTypes.STATIC_KEYWORD); final Annotation annotation = myHolder.createErrorAnnotation(astNode, JSBundle.message(key)); annotation.registerFix(new RemoveASTNodeFix("javascript.fix.remove.static.modifier", astNode)); } private static class AddOverrideIntentionAction implements IntentionAction { private final JSFunction myNode; public AddOverrideIntentionAction(final JSFunction node) { myNode = node; } @NotNull public String getText() { return JSBundle.message("javascript.fix.add.override.modifier"); } @NotNull public String getFamilyName() { return getText(); } public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { return myNode.isValid(); } public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { JSAttributeListWrapper w = new JSAttributeListWrapper(myNode.getAttributeList()); w.overrideModifier(JSAttributeList.ModifierType.OVERRIDE, true); w.applyTo(myNode); } public boolean startInWriteAction() { return true; } } public void visitJSPackageStatement(final JSPackageStatement packageStatement) { final JSFile jsFile = PsiTreeUtil.getParentOfType(packageStatement, JSFile.class); final PsiElement context = jsFile == null ? null : jsFile.getContext(); boolean injected = context instanceof XmlAttributeValue || context instanceof XmlText; if (injected) { myHolder.createErrorAnnotation(packageStatement.getFirstChild().getNode(), JSBundle.message("javascript.validation.message.nested.packages.are.not.allowed")); return; } for (PsiElement el = packageStatement.getPrevSibling(); el != null; el = el.getPrevSibling()) { if (!(el instanceof PsiWhiteSpace) && !(el instanceof PsiComment)) { myHolder.createErrorAnnotation( packageStatement.getFirstChild().getNode(), JSBundle.message("javascript.validation.message.package.shouldbe.first.statement") ); break; } } final ASTNode node = packageStatement.findNameIdentifier(); if (node == null) checkPackageStatement(packageStatement); } private void checkPackageStatement(final JSPackageStatement packageStatement) { final String s = packageStatement.getQualifiedName(); final PsiFile containingFile = packageStatement.getContainingFile(); final String expected = JSResolveUtil.getExpectedPackageNameFromFile(containingFile.getVirtualFile(), containingFile.getProject()); if (expected != null && (s == null && expected.length() != 0 || s != null && !expected.equals(s))) { final ASTNode nameIdentifier = packageStatement.findNameIdentifier(); final Annotation annotation = myHolder.createErrorAnnotation( nameIdentifier != null ? nameIdentifier : packageStatement.getFirstChild().getNode(), JSBundle.message( "javascript.validation.message.incorrect.package.name", s, expected ) ); annotation.registerFix(new IntentionAction() { @NotNull public String getText() { return JSBundle.message("javascript.fix.package.name", expected); } @NotNull public String getFamilyName() { return getText(); } public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { return packageStatement.isValid(); } public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { JSPackageStatementImpl.doChangeName(project, packageStatement, expected); } public boolean startInWriteAction() { return true; } }); } final Set<JSNamedElement> elements = new THashSet<>(); for (JSSourceElement statement : packageStatement.getStatements()) { if (statement instanceof JSNamedElement && !(statement instanceof JSImportStatement)) { elements.add((JSNamedElement)statement); } else if (statement instanceof JSVarStatement) { ContainerUtil.addAll(elements, ((JSVarStatement)statement).getVariables()); } } if (elements.size() > 1) { for (JSNamedElement el : elements) { if (!(el instanceof JSAttributeListOwner)) continue; JSAttributeList attributeList = ((JSAttributeListOwner)el).getAttributeList(); if (attributeList != null && attributeList.getConditionalCompileVariableReference() != null) continue; final ASTNode nameIdentifier = el.findNameIdentifier(); myHolder.createErrorAnnotation( nameIdentifier != null ? nameIdentifier : el.getFirstChild().getNode(), JSBundle.message("javascript.validation.message.more.than.one.externally.visible.symbol") ).registerFix(new RemoveASTNodeFix("javascript.fix.remove.externally.visible.symbol", el.getNode())); } } checkFileUnderSourceRoot(packageStatement, new SimpleErrorReportingClient()); } @Override public void visitJSReferenceExpression(JSReferenceExpression node) { super.visitJSReferenceExpression(node); final PsiElement parent = node.getParent(); if (node.getQualifier() == null) { String nodeText = node.getText(); if (!(parent instanceof JSCallExpression) && JSResolveUtil.isExprInStrictTypeContext(node) && JSCommonTypeNames.VECTOR_CLASS_NAME.equals(nodeText)) { myHolder.createWarningAnnotation(node, JSBundle.message("javascript.validation.message.vector.without.parameters")); } else if (parent instanceof JSNewExpression && JSCommonTypeNames.VECTOR_CLASS_NAME.equals(nodeText)) { myHolder.createWarningAnnotation(node, JSBundle.message("javascript.validation.message.vector.without.parameters2")); } } if (parent instanceof JSNamedElement) { JSNamedElement namedElement = (JSNamedElement)parent; final ASTNode nameIdentifier = namedElement.findNameIdentifier(); if (nameIdentifier != null && nameIdentifier.getPsi() == node) { if (parent instanceof JSPackageStatement) { checkPackageStatement((JSPackageStatement)parent); } else if (!(parent instanceof JSImportStatement) && parent.getParent() instanceof JSPackageStatement) { checkNamedObjectIsInCorrespondingFile(namedElement); } else if (parent instanceof JSVariable) { if (parent.getParent().getParent() instanceof JSPackageStatement) { checkNamedObjectIsInCorrespondingFile((JSVariable)parent); } } else if (parent instanceof JSNamespaceDeclaration) { DuplicatesCheckUtil.checkDuplicates((JSNamespaceDeclaration)parent, myProblemReporter); } if (parent instanceof JSClass) { final JSClass jsClass = (JSClass)parent; final JSFunction constructor = jsClass.getConstructor(); if (constructor == null) createConstructorChecker().checkMissedConstructor(jsClass); PsiElement clazzParent = jsClass.getParent(); final PsiElement context = clazzParent.getContext(); boolean clazzParentIsInjectedJsFile = clazzParent instanceof JSFile && (context instanceof XmlAttributeValue || context instanceof XmlText) && !XmlBackedJSClassImpl.isImplementsAttribute((JSFile)clazzParent); if (PsiTreeUtil.getParentOfType(jsClass, JSFunction.class, JSClass.class) != null || clazzParentIsInjectedJsFile) { myHolder.createErrorAnnotation(node, JSBundle.message("javascript.validation.message.nested.classes.are.not.allowed")); } checkClass(jsClass); } } } JSFunction fun; if (JSSymbolUtil.isAccurateReferenceExpressionName(node, JSFunction.ARGUMENTS_VAR_NAME) && (fun = PsiTreeUtil.getParentOfType(node, JSFunction.class)) != null && node.resolve() instanceof ImplicitJSVariableImpl) { JSParameterList parameterList = fun.getParameterList(); if (parameterList != null) { for (JSParameter p : parameterList.getParameterVariables()) { if (p.isRest()) { myHolder.createErrorAnnotation(node, JSBundle.message("javascript.validation.message.arguments.with.rest.parameter")); } } } } } private void checkClass(JSClass jsClass) { if (!jsClass.isInterface()) { checkIfExtendsFinalOrMultipleClasses(jsClass); } DuplicatesCheckUtil.checkDuplicates(jsClass, myProblemReporter); } private void checkIfExtendsFinalOrMultipleClasses(final JSClass jsClass) { final JSReferenceList extendsList = jsClass.getExtendsList(); if (extendsList != null) { final String[] extendsListTexts = extendsList.getReferenceTexts(); // in some cases jsClass.getSuperClasses() contains several elements for one class, so counting extendsListTexts is more correct if (extendsListTexts.length > 1) { myHolder .createErrorAnnotation(extendsList.getTextRange(), JSBundle.message("javascript.validation.message.extend.multiple.classes")); } else if (extendsListTexts.length == 1) { final JSClass[] superClasses = jsClass.getSuperClasses(); final JSAttributeList attributeList = superClasses.length > 0 ? superClasses[0].getAttributeList() : null; if (attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.FINAL)) { final JSExpression[] referencesToSuper = extendsList.getExpressions(); if (referencesToSuper.length == 1) { myHolder.createErrorAnnotation(referencesToSuper[0], JSBundle.message("javascript.validation.message.extend.final.class", superClasses[0].getQualifiedName())); } } } } } @Override public void visitJSAttributeList(JSAttributeList attributeList) { PsiElement namespaceElement = attributeList.getNamespaceElement(); PsiElement accessTypeElement = attributeList.findAccessTypeElement(); PsiElement namespaceOrAccessModifierElement = namespaceElement; ASTNode[] children = attributeList.getNode().getChildren(JSAttributeListImpl.ourModifiersTypeSet); if (namespaceOrAccessModifierElement == null) { namespaceOrAccessModifierElement = accessTypeElement; } else if (accessTypeElement != null) { myHolder.createErrorAnnotation(namespaceOrAccessModifierElement, JSBundle.message("javascript.validation.message.use.namespace.reference.or.access.modifier")) .registerFix( new RemoveASTNodeFix("javascript.fix.remove.namespace.reference", namespaceOrAccessModifierElement.getNode())); myHolder.createErrorAnnotation(accessTypeElement, JSBundle.message("javascript.validation.message.use.namespace.reference.or.access.modifier")) .registerFix(new RemoveASTNodeFix("javascript.fix.remove.visibility.modifier", accessTypeElement.getNode())); } if (children.length > 1 && namespaceElement == null) { for (ASTNode astNode : children) { myHolder.createErrorAnnotation(astNode, JSBundle.message("javascript.validation.message.one.visibility.modifier.allowed")) .registerFix(new RemoveASTNodeFix("javascript.fix.remove.visibility.modifier", astNode)); } } PsiElement element = attributeList.getParent(); PsiElement parentForCheckingNsOrAccessModifier = JSResolveUtil.findParent(element); if (namespaceOrAccessModifierElement != null) { if (!(parentForCheckingNsOrAccessModifier instanceof JSClass)) { String typeElementText; boolean nodeUnderPackage; if (!(nodeUnderPackage = parentForCheckingNsOrAccessModifier instanceof JSPackageStatement) && !hasQualifiedName(element) && (!(parentForCheckingNsOrAccessModifier instanceof JSFile) || attributeList.getAccessType() != JSAttributeList.AccessType.PACKAGE_LOCAL ) || !"public".equals(typeElementText = namespaceOrAccessModifierElement.getText()) && !"internal".equals(typeElementText)) { boolean nsRef = namespaceOrAccessModifierElement instanceof JSReferenceExpression; Annotation annotation; String message = JSBundle.message( nodeUnderPackage ? "javascript.validation.message.access.modifier.allowed.only.for.package.members" : nsRef ? "javascript.validation.message.namespace.allowed.only.for.class.members" : "javascript.validation.message.access.modifier.allowed.only.for.class.members"); if (parentForCheckingNsOrAccessModifier instanceof JSFile && !(element instanceof JSClass)) { // TODO: till we resolve issues with includes annotation = myHolder.createWarningAnnotation(namespaceOrAccessModifierElement, message); } else { annotation = myHolder.createErrorAnnotation( namespaceOrAccessModifierElement, message ); } annotation.registerFix(new RemoveASTNodeFix(nsRef ? "javascript.fix.remove.namespace.reference" : "javascript.fix.remove.access.modifier", namespaceOrAccessModifierElement.getNode())); } } else if (((JSClass)parentForCheckingNsOrAccessModifier).isInterface()) { if (attributeList.getAccessType() != JSAttributeList.AccessType.PACKAGE_LOCAL || attributeList.getNode().findChildByType(JSTokenTypes.INTERNAL_KEYWORD) != null ) { ASTNode astNode = attributeList.getNode().findChildByType(JSTokenTypes.ACCESS_MODIFIERS); String message = JSBundle.message("javascript.validation.message.interface.members.cannot.have.access.modifiers"); String fixMessageKey = "javascript.fix.remove.access.modifier"; if (astNode == null) { astNode = attributeList.getNode().findChildByType(JSElementTypes.REFERENCE_EXPRESSION); message = JSBundle.message("javascript.validation.message.interface.members.cannot.have.namespace.attributes"); fixMessageKey = "javascript.fix.remove.namespace.reference"; } final Annotation annotation = myHolder.createErrorAnnotation(astNode, message); annotation.registerFix(new RemoveASTNodeFix(fixMessageKey, astNode)); } } else if (JSResolveUtil.isConstructorFunction(element)) { JSAttributeList.AccessType accessType = attributeList.getAccessType(); if (accessType != JSAttributeList.AccessType.PUBLIC) { myHolder.createErrorAnnotation( namespaceOrAccessModifierElement.getNode(), JSBundle.message("javascript.validation.message.constructor.cannot.have.custom.visibility") ); } } } if (attributeList.hasModifier(JSAttributeList.ModifierType.FINAL)) { PsiElement parent; if (element instanceof JSClass) { if (((JSClass)element).isInterface()) { finalModifierProblem(attributeList, "javascript.validation.message.interface.cannot.be.final.modifiers"); } } else if (parentForCheckingNsOrAccessModifier instanceof JSClass && ((JSClass)parentForCheckingNsOrAccessModifier).isInterface()) { finalModifierProblem(attributeList, "javascript.validation.message.interface.members.cannot.be.final.modifiers"); } else if (!(element instanceof JSFunction) || (parent = element.getParent()) instanceof JSPackageStatement || parent instanceof JSFile && parent.getContext() == null) { finalModifierProblem(attributeList, "javascript.validation.message.final.modifier.allowed.only.for.methods"); } } if (attributeList.hasExplicitModifier(JSAttributeList.ModifierType.STATIC)) { if (element instanceof JSFunction || element instanceof JSVarStatement) { if (!(parentForCheckingNsOrAccessModifier instanceof JSClass)) { PsiElement modifierElement = attributeList.findModifierElement(JSAttributeList.ModifierType.STATIC); String message = JSBundle.message("javascript.validation.message.static.modifier.is.allowed.only.for.class.members"); Annotation annotation; if (parentForCheckingNsOrAccessModifier instanceof JSFile) { annotation = myHolder.createWarningAnnotation(modifierElement, message); } else { annotation = myHolder.createErrorAnnotation(modifierElement, message); } annotation.registerFix(new RemoveASTNodeFix("javascript.fix.remove.static.modifier", modifierElement.getNode())); } else if (JSResolveUtil.isConstructorFunction(element)) { modifierProblem(attributeList, JSAttributeList.ModifierType.STATIC, "javascript.validation.message.constructor.cannot.be.static", "javascript.fix.remove.static.modifier"); } } else if (element instanceof JSNamespaceDeclaration || element instanceof JSClass) { modifierProblem(attributeList, JSAttributeList.ModifierType.STATIC, "javascript.validation.message.static.modifier.is.allowed.only.for.class.members", "javascript.fix.remove.static.modifier"); } if (attributeList.hasModifier(JSAttributeList.ModifierType.FINAL) && element instanceof JSFunction) { finalModifierProblem( attributeList, "javascript.validation.message.static.method.cannot.be.final" ); } } if (attributeList.hasModifier(JSAttributeList.ModifierType.OVERRIDE) && !(element instanceof JSFunction) ) { modifierProblem(attributeList, JSAttributeList.ModifierType.OVERRIDE, "javascript.validation.message.override.can.be.applied.to.method", "javascript.fix.remove.override.modifier"); } if (attributeList.hasModifier(JSAttributeList.ModifierType.DYNAMIC) && (!(element instanceof JSClass) || ((JSClass)element).isInterface())) { modifierProblem(attributeList, JSAttributeList.ModifierType.DYNAMIC, "javascript.validation.message.dynamic.can.be.applied.to.class", "javascript.fix.remove.dynamic.modifier"); } checkMultipleModifiersProblem(attributeList); } private void finalModifierProblem(JSAttributeList attributeList, String messageKey) { modifierProblem(attributeList, JSAttributeList.ModifierType.FINAL, messageKey, "javascript.fix.remove.final.modifier"); } private void modifierProblem(JSAttributeList attributeList, JSAttributeList.ModifierType modifierType, String messageKey, String removeFixNameKey) { PsiElement modifierElement = attributeList.findModifierElement(modifierType); String message = JSBundle.message(messageKey); Annotation annotation = myHolder.createErrorAnnotation(modifierElement, message); annotation.registerFix(new RemoveASTNodeFix(removeFixNameKey, modifierElement.getNode())); } private static boolean hasQualifiedName(PsiElement element) { String qName = element instanceof JSQualifiedNamedElement ? ((JSQualifiedNamedElement)element).getQualifiedName() : null; return qName != null && qName.indexOf('.') != -1; } private static final List<TokenSet> ourModifiersList = Arrays.asList(TokenSet.create(JSTokenTypes.DYNAMIC_KEYWORD), TokenSet.create(JSTokenTypes.STATIC_KEYWORD), TokenSet.create(JSTokenTypes.FINAL_KEYWORD), TokenSet.create(JSTokenTypes.OVERRIDE_KEYWORD), TokenSet.create(JSTokenTypes.VIRTUAL_KEYWORD)); private static final String[] ourModifierFixIds = {"javascript.fix.remove.dynamic.modifier", "javascript.fix.remove.static.modifier", "javascript.fix.remove.final.modifier", "javascript.fix.remove.override.modifier", "javascript.fix.remove.virtual.modifier"}; private void checkMultipleModifiersProblem(JSAttributeList attributeList) { final ASTNode node = attributeList.getNode(); for (int i = 0; i < ourModifiersList.size(); ++i) { final ASTNode[] modifiers = node.getChildren(ourModifiersList.get(i)); if (modifiers.length < 2) continue; String s = modifiers[0].getElementType().toString().toLowerCase(Locale.ENGLISH); final String type = s.substring(s.indexOf(':') + 1, s.indexOf('_')); for (ASTNode a : modifiers) { final Annotation errorAnnotation = JSAnnotatorProblemReporter.createErrorAnnotation(a.getPsi(), JSBundle.message( "javascript.validation.message.attribute.was.specified.multiple.times", type), ProblemHighlightType.ERROR, myHolder ); errorAnnotation.registerFix(new RemoveASTNodeFix(ourModifierFixIds[i], a)); } } } @Nullable @Override protected LocalQuickFix getPreferredQuickFixForUnresolvedRef(final PsiElement nameIdentifier) { final Module module = ModuleUtilCore.findModuleForPsiElement(nameIdentifier); if (module == null || ModuleType.get(module) != FlexModuleType.getInstance()) return null; final String conditionalCompilerDefinitionName = getPotentialConditionalCompilerDefinitionName(nameIdentifier); if (conditionalCompilerDefinitionName != null) { return new DeclareConditionalCompilerDefinitionFix(module, conditionalCompilerDefinitionName); } final JSCallExpression callExpression = PsiTreeUtil.getParentOfType(nameIdentifier, JSCallExpression.class); if (callExpression == null) return null; if (JSResolveUtil.isEventListenerCall(callExpression)) { final JSExpression[] params = callExpression.getArguments(); if (params.length >= 2 && PsiTreeUtil.isAncestor(params[1], nameIdentifier, true)) { return new CreateJSEventMethod(nameIdentifier.getText(), () -> { PsiElement responsibleElement = null; if (params[0] instanceof JSReferenceExpression) { responsibleElement = ((JSReferenceExpression)params[0]).getQualifier(); } return responsibleElement == null ? FlexCommonTypeNames.FLASH_EVENT_FQN : responsibleElement.getText(); }); } } else if (needsFlexMobileViewAsFirstArgument(callExpression)) { final JSExpression[] params = callExpression.getArguments(); if (params.length >= 1 && PsiTreeUtil.isAncestor(params[0], nameIdentifier, true)) { final String contextPackage = JSResolveUtil.getPackageNameFromPlace(callExpression); final String fqn = StringUtil.getQualifiedName(contextPackage, nameIdentifier.getText()); final CreateFlexMobileViewIntentionAndFix fix = new CreateFlexMobileViewIntentionAndFix(fqn, nameIdentifier, true); fix.setCreatedClassFqnConsumer(fqn1 -> { final String packageName = StringUtil.getPackageName(fqn1); if (StringUtil.isNotEmpty(packageName) && !packageName.equals(contextPackage)) { ImportUtils.doImport(nameIdentifier, fqn1, true); } }); return fix; } } return null; } @Nullable private static String getPotentialConditionalCompilerDefinitionName(final PsiElement identifier) { final PsiElement parent1 = identifier.getParent(); final PsiElement parent2 = parent1 == null ? null : parent1.getParent(); final PsiElement parent3 = parent2 == null ? null : parent2.getParent(); if (parent1 instanceof JSReferenceExpression && ((JSReferenceExpression)parent1).getQualifier() == null && parent2 instanceof JSE4XNamespaceReference && parent3 instanceof JSReferenceExpression && ((JSReferenceExpression)parent3).getQualifier() == null) { return getNormalizedConditionalCompilerDefinitionName(parent3.getText()); } return null; } @Nullable private static String getNormalizedConditionalCompilerDefinitionName(final String name) { final int colonsIndex = name.indexOf("::"); if (colonsIndex > 0) { final String first = name.substring(0, colonsIndex).trim(); final String second = name.substring(colonsIndex + "::".length()).trim(); if (StringUtil.isJavaIdentifier(first) && StringUtil.isJavaIdentifier(second)) { return first + "::" + second; } } return null; } private static boolean needsFlexMobileViewAsFirstArgument(final JSCallExpression callExpression) { final JSExpression methodExpr = callExpression.getMethodExpression(); final PsiElement function = methodExpr instanceof JSReferenceExpression ? ((JSReferenceExpression)methodExpr).resolve() : null; final PsiElement clazz = function instanceof JSFunction && ArrayUtil.contains(((JSFunction)function).getName(), "pushView", "replaceView") ? function.getParent() : null; return clazz instanceof JSClass && "spark.components.ViewNavigator".equals(((JSClass)clazz).getQualifiedName()); } @Override public void visitJSAttribute(JSAttribute jsAttribute) { if ("Embed".equals(jsAttribute.getName())) { JSVarStatement varStatement = PsiTreeUtil.getParentOfType(jsAttribute, JSVarStatement.class); if (varStatement != null) { JSVariable var = ArrayUtil.getFirstElement(varStatement.getVariables()); if (var != null) { JSType type = var.getType(); if (!(type instanceof JSStringType) && !(type instanceof JSTypeImpl && "Class".equals(type.getTypeText(JSType.TypeTextFormat.SIMPLE)))) { myHolder.createErrorAnnotation(jsAttribute, JSBundle.message("javascript.validation.message.embed.annotation.used.with.var.of.wrong.type")); } } } } JSSemanticHighlightingUtil.highlight(jsAttribute, myHolder); PsiReference psiReference = jsAttribute.getReference(); if (psiReference != null && psiReference.resolve() == null) { myHolder.createWeakWarningAnnotation( jsAttribute.getNameIdentifier(), JSBundle.message("javascript.validation.message.unknown.metadata.annotation.used") ); } } @Override public void visitJSNamespaceDeclaration(JSNamespaceDeclaration namespaceDeclaration) { final PsiElement initializer = namespaceDeclaration.getInitializer(); if (initializer instanceof JSExpression) { PsiElement resolve; if (initializer instanceof JSLiteralExpression || initializer instanceof JSReferenceExpression && ((resolve = ((JSReferenceExpression)initializer).resolve()) instanceof JSNamespaceDeclaration || resolve instanceof JSVariable && "Namespace".equals(((JSVariable)resolve).getTypeString()) ) ) { // ok } else { JSAnnotatorProblemReporter.createErrorAnnotation( initializer, JSBundle.message("javascript.namespace.initializer.should.be.string.or.another.namespace.reference"), ProblemHighlightType.ERROR, myHolder ); } } } private void checkNamedObjectIsInCorrespondingFile(final JSNamedElement aClass) { final PsiFile containingFile = aClass.getContainingFile(); if (containingFile.getContext() != null) return; final VirtualFile file = containingFile.getVirtualFile(); if (file != null && !file.getNameWithoutExtension().equals(aClass.getName()) && ProjectRootManager.getInstance(containingFile.getProject()).getFileIndex().getSourceRootForFile(file) != null) { final ASTNode node = aClass.findNameIdentifier(); if (node != null) { final String name = aClass.getName(); String nameWithExtension = name + "." + file.getExtension(); final String message = JSBundle.message(aClass instanceof JSClass ? "javascript.validation.message.class.should.be.in.file" : aClass instanceof JSNamespaceDeclaration ? "javascript.validation.message.namespace.should.be.in.file" : aClass instanceof JSVariable ? "javascript.validation.message.variable.should.be.in.file" : "javascript.validation.message.function.should.be.in.file", name, nameWithExtension); final Annotation annotation = myHolder.createErrorAnnotation(node, message); annotation.registerFix(new RenameFileFix(nameWithExtension)); annotation.registerFix(new RenameElementFix(aClass) { final String text; final String familyName; { String term = message.substring(0, message.indexOf(' ')); text = super.getText().replace("class", StringUtil.decapitalize(term)); familyName = super.getFamilyName().replace("Class", term); } @NotNull @Override public String getText() { return text; } @NotNull @Override public String getFamilyName() { return familyName; } }); } } checkFileUnderSourceRoot(aClass, new SimpleErrorReportingClient()); } @Override protected void validateSetter(@NotNull JSFunction setter, @NotNull JSFunction getter, JSParameterListElement param, JSType setterType, JSType retType) { super.validateSetter(setter, getter, param, setterType, retType); checkAccessorAccessTypeMatch(setter, getter, "actionscript.validation.message.set.method.access.type.is.different.from.getter"); } protected void validateGetter(@NotNull JSFunction getter, JSFunction setter, JSType type) { if (type instanceof JSVoidType) { // TODO: fix! final String typeString = type != null ? type.getTypeText(JSType.TypeTextFormat.PRESENTABLE) : "empty"; myHolder.createErrorAnnotation( type != null ? getter.getReturnTypeElement() : getPlaceForNamedElementProblem(getter), JSBundle .message("javascript.validation.message.get.method.should.be.valid.type", typeString)); } else { if (setter != null) { JSParameterList setterParameterList = setter.getParameterList(); JSParameter[] setterParameters = setterParameterList != null ? setterParameterList.getParameterVariables() : JSParameter.EMPTY_ARRAY; JSType setterType; if (setterParameters.length == 1 && !((setterType = setterParameters[0].getType()) instanceof JSAnyType) && !(type instanceof JSAnyType) && !JSTypeUtils.areTypesCompatible(setterType, type, null, getter)) { PsiElement typeElement = getter.getReturnTypeElement(); myHolder.createErrorAnnotation( typeElement != null ? typeElement : getPlaceForNamedElementProblem(getter), JSBundle.message("javascript.validation.message.get.method.type.is.different.from.setter", setterType != null ? setterType.getTypeText(JSType.TypeTextFormat.PRESENTABLE) : "empty") ); } checkAccessorAccessTypeMatch(getter, setter, "actionscript.validation.message.get.method.access.type.is.different.from.setter"); } } } protected void validateRestParameterType(JSParameter parameter) { PsiElement typeElement = parameter.getTypeElement(); if (typeElement != null && !"Array".equals(typeElement.getText())) { final Pair<ASTNode, ASTNode> nodesBefore = getNodesBefore(typeElement, JSTokenTypes.COLON); myHolder.createErrorAnnotation( typeElement, JSBundle.message("javascript.validation.message.unexpected.type.for.rest.parameter") ).registerFix(JSFixFactory.getInstance().removeASTNodeFix("javascript.fix.remove.type.reference", false, nodesBefore.first, nodesBefore.second)); } } @Override protected boolean addCreateFromUsageFixes(JSReferenceExpression node, ResolveResult[] resolveResults, List<LocalQuickFix> fixes, boolean inTypeContext, boolean ecma) { final PsiElement nodeParent = node.getParent(); final JSExpression qualifier = node.getQualifier(); PsiElement nameIdentifier = node.getReferenceNameElement(); final String referencedName = nameIdentifier.getText(); inTypeContext = super.addCreateFromUsageFixes(node, resolveResults, fixes, inTypeContext, ecma); if (!(nodeParent instanceof JSArgumentList) && nodeParent.getParent() instanceof JSCallExpression) { inTypeContext = true; } if (!inTypeContext) { boolean getter = !(node.getParent() instanceof JSDefinitionExpression); String invokedName = nameIdentifier.getText(); fixes.add(new CreateJSPropertyAccessorIntentionAction(invokedName, getter)); } if (qualifier == null) { boolean canHaveTypeFix = false; JSClass contextClass = JSResolveUtil.getClassOfContext(node); if (nodeParent instanceof JSReferenceListMember) { canHaveTypeFix = true; fixes.add(new CreateClassOrInterfaceFix(node, contextClass.isInterface() || nodeParent.getParent().getNode().getElementType() == JSStubElementTypes.IMPLEMENTS_LIST, null, null)); } else if (!(nodeParent instanceof JSDefinitionExpression) && resolveResults.length == 0) { canHaveTypeFix = true; fixes.add(new CreateClassOrInterfaceFix(node, false, null, null)); fixes.add(new CreateClassOrInterfaceFix(node, true, null, null)); } if (!inTypeContext && JSReadWriteAccessDetector.ourInstance.getExpressionAccess(node) == ReadWriteAccessDetector.Access.Read) { canHaveTypeFix = true; fixes.add(new CreateJSFunctionIntentionAction(referencedName, true)); } if (canHaveTypeFix) fixes.add(new AddImportECMAScriptClassOrFunctionAction(null, node)); } else if (canHaveImportTo(resolveResults)) { fixes.add(new AddImportECMAScriptClassOrFunctionAction(null, node)); } return inTypeContext; } @Override protected void addCreateFromUsageFixesForCall(JSCallExpression node, JSReferenceExpression referenceExpression, ResolveResult[] resolveResults, List<LocalQuickFix> quickFixes) { if (canHaveImportTo(resolveResults)) { quickFixes.add(new AddImportECMAScriptClassOrFunctionAction(null, referenceExpression)); } super.addCreateFromUsageFixesForCall(node, referenceExpression, resolveResults, quickFixes); } private static boolean canHaveImportTo(ResolveResult[] resolveResults) { if (resolveResults.length == 0) return true; for (ResolveResult r : resolveResults) { if (!r.isValidResult()) { if (r instanceof JSResolveResult && ((JSResolveResult)r).getResolveProblemKey() == JSResolveResult.QUALIFIED_NAME_IS_NOT_IMPORTED) { return true; } continue; } PsiElement element = r.getElement(); if (element instanceof JSClass) return true; if (element instanceof JSFunction) { if (((JSFunction)element).isConstructor()) return true; } } return false; } @Override public void visitJSBinaryExpression(JSBinaryExpression node) { super.visitJSBinaryExpression(node); IElementType sign = node.getOperationSign(); JSExpression lOperand = node.getLOperand(); if (lOperand == null) return; final JSExpression rOperand = node.getROperand(); if (rOperand == null) return; if (sign == JSTokenTypes.AS_KEYWORD || sign == JSTokenTypes.IS_KEYWORD) { if (rOperand instanceof JSReferenceExpression) { ResolveResult[] results = ((JSReferenceExpression)rOperand).multiResolve(false); PsiElement resolve; if (results.length > 0 && ((resolve=results[0].getElement()) instanceof JSVariable || resolve instanceof JSFunction)) { checkIfProperTypeReference(rOperand); } } else { checkIfProperTypeReference(rOperand); } } else if (JSTokenTypes.MULTIPLICATIVE_OPERATIONS.contains(sign) || JSTokenTypes.SHIFT_OPERATIONS.contains(sign) || sign == JSTokenTypes.MINUS) { final JSType numberType = JSNamedType.createType(NUMBER_CLASS_NAME, JSTypeSourceFactory.createTypeSource(lOperand, true), JSContext.INSTANCE); myTypeChecker.checkExpressionIsAssignableToType(lOperand, numberType, "javascript.expression.type.implicitly.coerced.to.unrelated.type", null); myTypeChecker.checkExpressionIsAssignableToType(rOperand, numberType, "javascript.expression.type.implicitly.coerced.to.unrelated.type", null); } } private void checkIfProperTypeReference(JSExpression rOperand) { ValidateTypesUtil.checkTypeIs( rOperand, rOperand, myProblemReporter, "Class", "javascript.binary.operand.type.mismatch" ); } public void visitJSThisExpression(final JSThisExpression node) { checkClassReferenceInStaticContext(node, "javascript.validation.message.this.referenced.from.static.context"); } @Override protected boolean processMethodExpressionResolveResult(JSCallExpression callExpression, JSReferenceExpression methodExpression, PsiElement resolveResult, JSType type) { if (callExpression instanceof JSNewExpression) { if (JSResolveUtil.isConstructorFunction(resolveResult)) { resolveResult = resolveResult.getParent(); // TODO: there is no need once our stubs for interface will lose constructors for interfaces } if (resolveResult instanceof JSClass && ((JSClass)resolveResult).isInterface()) { final PsiElement referenceNameElement = methodExpression.getReferenceNameElement(); reportUnresolvedFunctionError(referenceNameElement, this.getUnresolvedReferenceHighlightType(methodExpression), JSBundle.message("javascript.interface.can.not.be.instantiated.message"), null); return false; } } final JSArgumentList argumentList = callExpression.getArgumentList(); if (argumentList instanceof JSE4XFilterQueryArgumentList && (type != null && !(type instanceof JSAnyType) || resolveResult instanceof JSFunction && ((JSFunction)resolveResult).isGetProperty())) { checkE4XFilterQuery(callExpression, type.getTypeText(), argumentList); return false; } return true; } private void checkE4XFilterQuery(JSCallExpression node, String type, JSArgumentList argumentList) { if (!JSResolveUtil.isAssignableType("XML", type, argumentList) && !JSResolveUtil.isAssignableType("XMLList", type, argumentList) ) { myTypeChecker.registerProblem( node.getMethodExpression(), JSBundle.message("javascript.invalid.e4x.filter.query.receiver", type), ProblemHighlightType.GENERIC_ERROR ); return; } myFunctionSignatureChecker.reportProblemIfNotExpectedCountOfParameters(node, 1, "one"); JSExpression[] arguments = argumentList.getArguments(); if (arguments.length >= 1) { myTypeChecker.checkExpressionIsAssignableToType( arguments[0], BOOLEAN_CLASS_NAME, "javascript.argument.type.mismatch", null); } } @Nullable @Override protected JSType getResolveResultType(JSExpression qualifier, PsiElement resultElement) { if (resultElement instanceof JSVariable) { // do not evaluate initializer return ((JSVariable)resultElement).getType(); } return super.getResolveResultType(qualifier, resultElement); } @Nullable @Override public ProblemHighlightType getUnresolvedReferenceHighlightType(@NotNull JSReferenceExpression node) { JSExpression qualifier = ((JSReferenceExpressionImpl)node).getResolveQualifier(); if (qualifier != null) { final PsiFile containingFile = node.getContainingFile(); JSType type = null; boolean checkType = false; if (qualifier instanceof JSReferenceExpression) { ResolveResult[] results = ((JSReferenceExpression)qualifier).multiResolve(false); if (results.length != 0) { PsiElement resultElement = results[0].getElement(); if (resultElement instanceof JSPackageWrapper) return ProblemHighlightType.ERROR; type = getResolveResultType(qualifier, resultElement); checkType = true; } } else { type = JSResolveUtil.getExpressionJSType(qualifier); checkType = true; } if (checkType && (type instanceof JSAnyType || type == null)) { return ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; } JSClass jsClass = JSResolveUtil.findClassOfQualifier(qualifier, containingFile); if (jsClass == null) { return ProblemHighlightType.ERROR; } final JSAttributeList attributeList = jsClass.getAttributeList(); if (attributeList == null || !attributeList.hasModifier(JSAttributeList.ModifierType.DYNAMIC)) { return ProblemHighlightType.ERROR; } final String qualifiedName = jsClass.getQualifiedName(); if ("Error".equals(qualifiedName) || "Date".equals(qualifiedName)) { return ProblemHighlightType.GENERIC_ERROR_OR_WARNING; } } return super.getUnresolvedReferenceHighlightType(node); } private void checkClassReferenceInStaticContext(final JSExpression node, @PropertyKey(resourceBundle = JSBundle.BUNDLE) String key) { PsiElement element = PsiTreeUtil.getParentOfType(node, JSExecutionScope.class, JSClass.class, JSObjectLiteralExpression.class); if (element instanceof JSFunction) { final JSFunction function = (JSFunction)element; final JSAttributeList attributeList = function.getAttributeList(); if (attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.STATIC)) { myHolder.createErrorAnnotation(node, JSBundle.message(key)); return; } } if (node instanceof JSSuperExpression) { if (element instanceof JSObjectLiteralExpression) { element = PsiTreeUtil.getParentOfType(node, JSExecutionScope.class, JSClass.class); } if(element == null || !(element instanceof JSClass) && !(JSResolveUtil.findParent(element) instanceof JSClass) ) { final String message = JSBundle.message("javascript.validation.message.super.referenced.without.class.instance.context"); myHolder.createErrorAnnotation(node, message); } } } @Override public void visitJSSuperExpression(final JSSuperExpression node) { checkClassReferenceInStaticContext(node, "javascript.validation.message.super.referenced.from.static.context"); } @Override public void visitJSReturnStatement(@NotNull JSReturnStatement node) { super.visitJSReturnStatement(node); final PsiElement element = PsiTreeUtil.getParentOfType(node, JSFunction.class, XmlTagChild.class, XmlAttributeValue.class, JSFile.class); if (element instanceof JSFunction) { JSExpression returnedExpr = node.getExpression(); if (returnedExpr != null && ((JSFunction)element).isConstructor() && JSResolveUtil.findParent(element) instanceof JSClass) { final String message = FlexBundle.message("javascript.validation.message.no.return.value.required.for.constructor"); myHolder.createErrorAnnotation(returnedExpr, message); } } } @Override public void visitJSForInStatement(JSForInStatement node) { super.visitJSForInStatement(node); ValidateTypesUtil.checkTypesInForIn(node, myProblemReporter); } @Override protected boolean isConstAssignable(@NotNull JSReferenceExpression lExpr, PsiElement resolved) { return false; } @Override protected boolean isConstNeedInitializer(JSVariable var) { return true; } }