package com.siberika.idea.pascal.lang; import com.intellij.lang.annotation.Annotation; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.SmartList; import com.siberika.idea.pascal.editor.PascalActionDeclare; import com.siberika.idea.pascal.editor.PascalRoutineActions; import com.siberika.idea.pascal.ide.actions.AddFixType; import com.siberika.idea.pascal.ide.actions.SectionToggle; import com.siberika.idea.pascal.ide.actions.UsesActions; import com.siberika.idea.pascal.lang.parser.NamespaceRec; import com.siberika.idea.pascal.lang.psi.PasClassPropertySpecifier; import com.siberika.idea.pascal.lang.psi.PasConstExpression; import com.siberika.idea.pascal.lang.psi.PasEntityScope; import com.siberika.idea.pascal.lang.psi.PasEnumType; import com.siberika.idea.pascal.lang.psi.PasModule; import com.siberika.idea.pascal.lang.psi.PascalNamedElement; import com.siberika.idea.pascal.lang.psi.PascalQualifiedIdent; import com.siberika.idea.pascal.lang.psi.PascalStructType; import com.siberika.idea.pascal.lang.psi.impl.PasExportedRoutineImpl; import com.siberika.idea.pascal.lang.psi.impl.PasField; import com.siberika.idea.pascal.lang.psi.impl.PasRoutineImplDeclImpl; import com.siberika.idea.pascal.lang.psi.impl.PascalRoutineImpl; import com.siberika.idea.pascal.lang.references.PasReferenceUtil; import com.siberika.idea.pascal.util.PsiContext; import com.siberika.idea.pascal.util.PsiUtil; import com.siberika.idea.pascal.util.StrUtil; import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.Set; import static com.siberika.idea.pascal.PascalBundle.message; /** * Author: George Bakhtadze * Date: 12/14/12 */ public class PascalAnnotator implements Annotator { public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if ((element instanceof PascalNamedElement) && (PsiUtil.isRoutineName((PascalNamedElement) element))) { PsiElement parent = element.getParent(); if (parent.getClass() == PasExportedRoutineImpl.class) { annotateRoutineInInterface((PasExportedRoutineImpl) parent, holder); } else if (parent.getClass() == PasRoutineImplDeclImpl.class) { annotateRoutineInImplementation((PasRoutineImplDeclImpl) parent, holder); } } //noinspection ConstantConditions if (PsiUtil.isEntityName(element) && !PsiUtil.isLastPartOfMethodImplName((PascalNamedElement) element)) { //noinspection ConstantConditions PascalNamedElement namedElement = (PascalNamedElement) element; List<PsiElement> scopes = new SmartList<PsiElement>(); Collection<PasField> refs = PasReferenceUtil.resolveExpr(scopes, NamespaceRec.fromElement(element), PasField.TYPES_ALL, true, 0); if (refs.isEmpty()) { Annotation ann = holder.createErrorAnnotation(element, message("ann.error.undeclared.identifier")); PsiContext context = PsiUtil.getContext(namedElement); Set<AddFixType> fixes = EnumSet.of(AddFixType.VAR, AddFixType.TYPE, AddFixType.CONST, AddFixType.ROUTINE); // [*] => var type const routine if (context == PsiContext.FQN_FIRST) { fixes.add(AddFixType.UNIT); } PsiElement scope = scopes.isEmpty() ? null : scopes.get(0); if (scope instanceof PasEnumType) { // TEnum.* => -* +enum fixes = EnumSet.of(AddFixType.ENUM); } else if (scope instanceof PascalRoutineImpl) { // [inRoutine] => +parameter fixes.add(AddFixType.PARAMETER); } if (context == PsiContext.TYPE_ID) { // [TypeIdent] => -* +type fixes = EnumSet.of(AddFixType.TYPE); } else if (PsiTreeUtil.getParentOfType(namedElement, PasConstExpression.class) != null) { // [part of const expr] => -* +const +enum fixes = EnumSet.of(AddFixType.CONST); } else if (context == PsiContext.EXPORT) { fixes = EnumSet.of(AddFixType.ROUTINE); } else if (context == PsiContext.CALL) { fixes = EnumSet.of(AddFixType.ROUTINE, AddFixType.VAR); } else if (context == PsiContext.PROPERTY_SPEC) { fixes = EnumSet.of(AddFixType.VAR, AddFixType.ROUTINE); } else if (context == PsiContext.FOR) { fixes = EnumSet.of(AddFixType.VAR); } if (context == PsiContext.USES) { fixes = EnumSet.of(AddFixType.NEW_UNIT); } String name = namedElement.getName(); for (AddFixType fix : fixes) { switch (fix) { case VAR: { boolean priority = context != PsiContext.CALL; if (!(scope instanceof PascalStructType)) { ann.registerFix(PascalActionDeclare.newActionCreateVar(message("action.create.var"), namedElement, null, priority, context != PsiContext.FOR ? null : "Integer")); } PsiElement adjustedScope = adjustScope(scope); if (adjustedScope instanceof PascalStructType) { if (StrUtil.PATTERN_FIELD.matcher(name).matches()) { ann.registerFix(PascalActionDeclare.newActionCreateVar(message("action.create.field"), namedElement, adjustedScope, priority, null)); if (context != PsiContext.PROPERTY_SPEC) { ann.registerFix(PascalActionDeclare.newActionCreateProperty(message("action.create.property"), namedElement, adjustedScope, false)); } } else { ann.registerFix(PascalActionDeclare.newActionCreateVar(message("action.create.field"), namedElement, adjustedScope, false, null)); if (context != PsiContext.PROPERTY_SPEC) { ann.registerFix(PascalActionDeclare.newActionCreateProperty(message("action.create.property"), namedElement, adjustedScope, priority)); } } } break; } case TYPE: { boolean priority = name.startsWith("T"); if (!(scope instanceof PascalStructType) || (context != PsiContext.FQN_NEXT)) { ann.registerFix(PascalActionDeclare.newActionCreateType(namedElement, null, priority)); priority = false; // lower priority for nested } ann.registerFix(PascalActionDeclare.newActionCreateType(namedElement, adjustScope(scope), priority)); break; } case CONST: { boolean priority = !StrUtil.hasLowerCaseChar(name); if ((scope instanceof PascalStructType)) { ann.registerFix(PascalActionDeclare.newActionCreateConst(namedElement, null, priority)); priority = false; // lower priority for nested } else { ann.registerFix(PascalActionDeclare.newActionCreateConst(namedElement, scope, priority)); } ann.registerFix(PascalActionDeclare.newActionCreateConst(namedElement, adjustScope(scope), priority)); break; } case ROUTINE: { boolean priority = context == PsiContext.CALL; if (scope instanceof PascalStructType) { if (context == PsiContext.PROPERTY_SPEC) { PasClassPropertySpecifier spec = PsiTreeUtil.getParentOfType(namedElement, PasClassPropertySpecifier.class); ann.registerFix(PascalActionDeclare.newActionCreateRoutine(message("action.create." + (PsiUtil.isPropertyGetter(spec) ? "getter" : "setter")), namedElement, scope, null, priority, spec)); } else { ann.registerFix(PascalActionDeclare.newActionCreateRoutine(message("action.create.method"), namedElement, scope, null, priority, null)); } } else { ann.registerFix(PascalActionDeclare.newActionCreateRoutine(message("action.create.routine"), namedElement, scope, null, priority, null)); PsiElement adjustedScope = adjustScope(scope); if (adjustedScope instanceof PascalStructType) { ann.registerFix(PascalActionDeclare.newActionCreateRoutine(message("action.create.method"), namedElement, adjustedScope, scope, priority, null)); } } break; } case ENUM: { ann.registerFix(new PascalActionDeclare.ActionCreateEnum(message("action.create.enumConst"), namedElement, scope)); break; } case PARAMETER: { ann.registerFix(new PascalActionDeclare.ActionCreateParameter(message("action.create.parameter"), namedElement, scope)); break; } case UNIT: { ann.registerFix(new UsesActions.AddUnitAction(message("action.add.uses"), namedElement.getName(), PsiUtil.belongsToInterface(namedElement))); break; } case NEW_UNIT: { ann.registerFix(new UsesActions.NewUnitAction(message("action.create.unit"), namedElement.getName())); break; } } } } } if ((element instanceof PascalNamedElement) && PsiUtil.isUsedUnitName(element.getParent())) { annotateUnit(holder, (PascalQualifiedIdent) element.getParent()); } } private PsiElement adjustScope(PsiElement scope) { if (scope instanceof PascalRoutineImpl) { PasEntityScope struct = ((PascalRoutineImpl) scope).getContainingScope(); if (struct instanceof PascalStructType) { return struct; } } return scope; } private void annotateUnit(AnnotationHolder holder, PascalQualifiedIdent usedUnitName) { if (PascalImportOptimizer.isExcludedFromCheck(usedUnitName)) { return; } Annotation ann = null; switch (PascalImportOptimizer.getUsedUnitStatus(usedUnitName, ModuleUtilCore.findModuleForPsiElement(usedUnitName))) { case UNUSED: { ann = holder.createWarningAnnotation(usedUnitName, message("ann.warn.unused.unit")); break; } case USED_IN_IMPL: { ann = holder.createWarningAnnotation(usedUnitName, message("ann.warn.unused.unit.interface")); ann.registerFix(new UsesActions.MoveUnitAction(message("action.uses.move"), usedUnitName)); break; } } if (ann != null) { ann.registerFix(new UsesActions.RemoveUnitAction(message("action.uses.remove"), usedUnitName)); ann.registerFix(new UsesActions.ExcludeUnitAction(message("action.uses.exclude"), usedUnitName)); ann.registerFix(new UsesActions.OptimizeUsesAction(message("action.uses.optimize"))); } } /** * # unimplemented routine error * # unimplemented method error * # filter external/abstract routines/methods * # implement routine fix * # implement method fix * error on class if not all methods implemented * implement all methods fix */ private void annotateRoutineInInterface(PasExportedRoutineImpl routine, AnnotationHolder holder) { if (!PsiUtil.isFromBuiltinsUnit(routine) && PsiUtil.needImplementation(routine) && (null == SectionToggle.retrieveImplementation(routine, true))) { Annotation ann = holder.createErrorAnnotation(routine, message("ann.error.missing.implementation")); ann.registerFix(new PascalRoutineActions.ActionImplement(message("action.implement"), routine)); ann.registerFix(new PascalRoutineActions.ActionImplementAll(message("action.implement.all"), routine)); } } /** * error on method in implementation only * add method to class declaration fix * add to interface section fix for routines in implementation section only */ private void annotateRoutineInImplementation(PasRoutineImplDeclImpl routine, AnnotationHolder holder) { if (null == SectionToggle.retrieveDeclaration(routine, true)) { if (routine.getContainingScope() instanceof PasModule) { if (((PasModule) routine.getContainingScope()).getUnitInterface() != null) { Annotation ann = holder.createInfoAnnotation(routine.getNameIdentifier() != null ? routine.getNameIdentifier() : routine, message("ann.error.missing.routine.declaration")); ann.registerFix(new PascalRoutineActions.ActionDeclare(message("action.declare.routine"), routine)); ann.registerFix(new PascalRoutineActions.ActionDeclareAll(message("action.declare.routine.all"), routine)); } } else { Annotation ann = holder.createErrorAnnotation(routine.getNameIdentifier() != null ? routine.getNameIdentifier() : routine, message("ann.error.missing.method.declaration")); ann.registerFix(new PascalRoutineActions.ActionDeclare(message("action.declare.method"), routine)); ann.registerFix(new PascalRoutineActions.ActionDeclareAll(message("action.declare.method.all"), routine)); } } } }