/* * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.goide.highlighting; import com.goide.GoConstants; import com.goide.GoTypes; import com.goide.inspections.GoInspectionUtil; import com.goide.psi.*; import com.goide.psi.impl.GoCType; import com.goide.psi.impl.GoPsiImplUtil; import com.goide.psi.impl.GoTypeUtil; import com.goide.quickfix.GoDeleteRangeQuickFix; import com.goide.quickfix.GoEmptySignatureQuickFix; import com.goide.quickfix.GoReplaceWithReturnStatementQuickFix; import com.google.common.collect.Sets; import com.intellij.lang.ASTNode; import com.intellij.lang.annotation.Annotation; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Set; public class GoAnnotator implements Annotator { private static final Set<String> INT_TYPE_NAMES = Sets.newHashSet( "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "rune", "float32", "float64" ); // todo: unify with DlvApi.Variable.Kind @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (!(element instanceof GoCompositeElement) || !element.isValid()) return; if (element instanceof GoPackageClause) { PsiElement identifier = ((GoPackageClause)element).getIdentifier(); if (identifier != null && identifier.textMatches("_")) { holder.createErrorAnnotation(identifier, "Invalid package name"); return; } } if (element instanceof GoContinueStatement) { if (!(PsiTreeUtil.getParentOfType(element, GoForStatement.class, GoFunctionLit.class) instanceof GoForStatement)) { Annotation annotation = holder.createErrorAnnotation(element, "Continue statement not inside a for loop"); annotation.registerFix(new GoReplaceWithReturnStatementQuickFix(element)); } } else if (element instanceof GoBreakStatement) { if (GoPsiImplUtil.getBreakStatementOwner(element) == null) { Annotation annotation = holder.createErrorAnnotation(element, "Break statement not inside a for loop, select or switch"); annotation.registerFix(new GoReplaceWithReturnStatementQuickFix(element)); } } else if (element instanceof GoReferenceExpression) { GoReferenceExpression reference = (GoReferenceExpression)element; PsiElement resolvedReference = reference.resolve(); if (resolvedReference instanceof PsiDirectory || resolvedReference instanceof GoImportSpec) { // It's a package reference. It should either be inside a package clause or part of a larger reference expression. if (!(element.getParent() instanceof GoReferenceExpression) && PsiTreeUtil.getParentOfType(reference, GoPackageClause.class) == null) { holder.createErrorAnnotation(element, "Use of package " + element.getText() + " without selector"); } } if (resolvedReference instanceof GoTypeSpec && isIllegalUseOfTypeAsExpression(reference)) { holder.createErrorAnnotation(element, "Type " + element.getText() + " is not an expression"); } if (resolvedReference instanceof GoConstDefinition && resolvedReference.getParent() instanceof GoConstSpec && PsiTreeUtil.getParentOfType(element, GoConstDeclaration.class) != null) { checkSelfReference((GoReferenceExpression)element, resolvedReference, holder); } if (resolvedReference instanceof GoVarDefinition && resolvedReference.getParent() instanceof GoVarSpec && PsiTreeUtil.getParentOfType(element, GoVarDeclaration.class) != null) { checkSelfReference((GoReferenceExpression)element, resolvedReference, holder); } } else if (element instanceof GoLiteralTypeExpr) { if (isIllegalUseOfTypeAsExpression(element)) { holder.createErrorAnnotation(element, "Type " + element.getText() + " is not an expression"); } } else if (element instanceof GoCompositeLit) { GoCompositeLit literal = (GoCompositeLit)element; if (literal.getType() instanceof GoMapType) { GoLiteralValue literalValue = literal.getLiteralValue(); if (literalValue != null) { for (GoElement literalElement : literalValue.getElementList()) { if (literalElement.getKey() == null) { holder.createErrorAnnotation(literalElement, "Missing key in map literal"); } } } } } else if (element instanceof GoTypeAssertionExpr) { GoType type = ((GoTypeAssertionExpr)element).getExpression().getGoType(null); if (type != null) { GoType underlyingType = type.getUnderlyingType(); if (!(underlyingType instanceof GoInterfaceType)) { String message = String.format("Invalid type assertion: %s, (non-interface type %s on left)", element.getText(), type.getText()); holder.createErrorAnnotation(((GoTypeAssertionExpr)element).getExpression(), message); } } } else if (element instanceof GoBuiltinCallExpr) { GoBuiltinCallExpr call = (GoBuiltinCallExpr)element; if ("make".equals(call.getReferenceExpression().getText())) { checkMakeCall(call, holder); } } else if (element instanceof GoCallExpr) { GoCallExpr call = (GoCallExpr)element; GoExpression callExpression = call.getExpression(); if (GoInspectionUtil.getFunctionResultCount(call) == 0) { PsiElement parent = call.getParent(); boolean simpleStatement = parent instanceof GoLeftHandExprList && parent.getParent() instanceof GoSimpleStatement; boolean inDeferOrGo = parent instanceof GoDeferStatement || parent instanceof GoGoStatement; if (!simpleStatement && !inDeferOrGo) { holder.createErrorAnnotation(call, call.getText() + " used as value"); } } if (callExpression instanceof GoReferenceExpression) { GoReferenceExpression reference = (GoReferenceExpression)callExpression; if (reference.textMatches("cap")) { if (GoPsiImplUtil.builtin(reference.resolve())) { checkCapCall(call, holder); } } } } else if (element instanceof GoTopLevelDeclaration) { if (element.getParent() instanceof GoFile) { if (element instanceof GoTypeDeclaration) { for (GoTypeSpec spec : ((GoTypeDeclaration)element).getTypeSpecList()) { if (spec.getIdentifier().textMatches(GoConstants.INIT)) { holder.createErrorAnnotation(spec, "Cannot declare init, must be a function"); } } } else if (element instanceof GoVarDeclaration) { for (GoVarSpec spec : ((GoVarDeclaration)element).getVarSpecList()) { for (GoVarDefinition definition : spec.getVarDefinitionList()) { if (definition.getIdentifier().textMatches(GoConstants.INIT)) { holder.createErrorAnnotation(spec, "Cannot declare init, must be a function"); } } } } else if (element instanceof GoConstDeclaration) { for (GoConstSpec spec : ((GoConstDeclaration)element).getConstSpecList()) { for (GoConstDefinition definition : spec.getConstDefinitionList()) { if (definition.getIdentifier().textMatches(GoConstants.INIT)) { holder.createErrorAnnotation(spec, "Cannot declare init, must be a function"); } } } } else if (element instanceof GoFunctionDeclaration) { GoFunctionDeclaration declaration = (GoFunctionDeclaration)element; if (declaration.getIdentifier().textMatches(GoConstants.INIT) || declaration.getIdentifier().textMatches(GoConstants.MAIN) && GoConstants.MAIN.equals(declaration.getContainingFile().getPackageName())) { GoSignature signature = declaration.getSignature(); if (signature != null) { GoResult result = signature.getResult(); if (result != null && !result.isVoid()) { Annotation annotation = holder.createErrorAnnotation(result, declaration.getName() + " function must have no arguments and no return values"); annotation.registerFix(new GoEmptySignatureQuickFix(declaration)); } GoParameters parameters = signature.getParameters(); if (!parameters.getParameterDeclarationList().isEmpty()) { Annotation annotation = holder.createErrorAnnotation(parameters, declaration.getName() + " function must have no arguments and no return values"); annotation.registerFix(new GoEmptySignatureQuickFix(declaration)); } } } } } } else if (element instanceof GoIndexOrSliceExpr) { GoIndexOrSliceExpr slice = (GoIndexOrSliceExpr)element; GoExpression expr = slice.getExpression(); GoExpression thirdIndex = slice.getIndices().third; if (expr == null || thirdIndex == null) { return; } if (GoTypeUtil.isString(expr.getGoType(null))) { ASTNode[] colons = slice.getNode().getChildren(TokenSet.create(GoTypes.COLON)); if (colons.length == 2) { PsiElement secondColon = colons[1].getPsi(); TextRange r = TextRange.create(secondColon.getTextRange().getStartOffset(), thirdIndex.getTextRange().getEndOffset()); Annotation annotation = holder.createErrorAnnotation(r, "Invalid operation " + slice.getText() + " (3-index slice of string)"); annotation.registerFix(new GoDeleteRangeQuickFix(secondColon, thirdIndex, "Delete third index")); } } } } private static void checkCapCall(@NotNull GoCallExpr capCall, @NotNull AnnotationHolder holder) { List<GoExpression> exprs = capCall.getArgumentList().getExpressionList(); if (exprs.size() != 1) return; GoExpression first = ContainerUtil.getFirstItem(exprs); //noinspection ConstantConditions GoType exprType = first.getGoType(null); // todo: context if (exprType == null) return; GoType baseType = GoPsiImplUtil.unwrapPointerIfNeeded(exprType.getUnderlyingType()); if (baseType instanceof GoArrayOrSliceType || baseType instanceof GoChannelType) return; holder.createErrorAnnotation(first, "Invalid argument for cap"); } private static void checkMakeCall(@NotNull GoBuiltinCallExpr call, @NotNull AnnotationHolder holder) { GoBuiltinArgumentList args = call.getBuiltinArgumentList(); if (args == null) { holder.createErrorAnnotation(call, "Missing argument to make"); return; } GoType type = args.getType(); if (type == null) { GoExpression first = ContainerUtil.getFirstItem(args.getExpressionList()); if (first != null) { holder.createErrorAnnotation(call, first.getText() + " is not a type"); } else { holder.createErrorAnnotation(args, "Missing argument to make"); } } else { // We have a type, is it valid? GoType baseType = type.getUnderlyingType(); if (canMakeType(baseType)) { // We have a type and we can make the type, are the parameters to make valid? checkMakeArgs(call, baseType, args.getExpressionList(), holder); } else { holder.createErrorAnnotation(type, "Cannot make " + type.getText()); } } } private static boolean canMakeType(@Nullable GoType type) { if (type instanceof GoArrayOrSliceType) { // Only slices (no size expression) can be make()'d. return ((GoArrayOrSliceType)type).getExpression() == null; } return type instanceof GoChannelType || type instanceof GoMapType; } private static void checkMakeArgs(@NotNull GoBuiltinCallExpr call, @Nullable GoType baseType, @NotNull List<GoExpression> list, @NotNull AnnotationHolder holder) { if (baseType instanceof GoArrayOrSliceType) { if (list.isEmpty()) { holder.createErrorAnnotation(call, "Missing len argument to make"); return; } else if (list.size() > 2) { holder.createErrorAnnotation(call, "Too many arguments to make"); return; } } if (baseType instanceof GoChannelType || baseType instanceof GoMapType) { if (list.size() > 1) { holder.createErrorAnnotation(call, "Too many arguments to make"); return; } } for (int i = 0; i < list.size(); i++) { GoExpression expression = list.get(i); GoType type = expression.getGoType(null); // todo: context if (type != null) { GoType expressionBaseType = type.getUnderlyingType(); if (!(isIntegerConvertibleType(expressionBaseType) || isCType(type))) { String argName = i == 0 ? "size" : "capacity"; holder.createErrorAnnotation(expression, "Non-integer " + argName + " argument to make"); } } } } private static boolean isCType(@Nullable GoType type) { return type instanceof GoCType; } private static boolean isIntegerConvertibleType(@Nullable GoType type) { if (type == null) return false; GoTypeReferenceExpression ref = type.getTypeReferenceExpression(); if (ref == null) return false; return INT_TYPE_NAMES.contains(ref.getText()) && GoPsiImplUtil.builtin(ref.resolve()); } private static void checkSelfReference(@NotNull GoReferenceExpression o, PsiElement definition, AnnotationHolder holder) { GoExpression value = null; if (definition instanceof GoVarDefinition) { value = ((GoVarDefinition)definition).getValue(); } else if (definition instanceof GoConstDefinition) { value = ((GoConstDefinition)definition).getValue(); } if (value != null && value.equals(GoPsiImplUtil.getNonStrictTopmostParentOfType(o, GoExpression.class))) { holder.createErrorAnnotation(o, "Cyclic definition detected"); } } /** * Returns {@code true} if the given element is in an invalid location for a type literal or type reference. */ private static boolean isIllegalUseOfTypeAsExpression(@NotNull PsiElement e) { PsiElement parent = PsiTreeUtil.skipParentsOfType(e, GoParenthesesExpr.class, GoUnaryExpr.class); // Part of a selector such as T.method if (parent instanceof GoReferenceExpression || parent instanceof GoSelectorExpr) return false; // A situation like T("foo"). return !(parent instanceof GoCallExpr); } }