/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2015 AS3Boyan
* Copyright 2014-2014 Elias Ku
*
* 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.intellij.plugins.haxe.ide.annotator;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.plugins.haxe.HaxeBundle;
import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes;
import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.model.*;
import com.intellij.plugins.haxe.model.fixer.HaxeFixer;
import com.intellij.plugins.haxe.model.fixer.HaxeModifierAddFixer;
import com.intellij.plugins.haxe.model.fixer.HaxeModifierRemoveFixer;
import com.intellij.plugins.haxe.model.fixer.HaxeModifierReplaceVisibilityFixer;
import com.intellij.plugins.haxe.model.type.HaxeTypeCompatible;
import com.intellij.plugins.haxe.model.type.HaxeTypeResolver;
import com.intellij.plugins.haxe.model.type.ResultHolder;
import com.intellij.plugins.haxe.util.HaxeAbstractEnumUtil;
import com.intellij.plugins.haxe.util.HaxeResolveUtil;
import com.intellij.plugins.haxe.util.PsiFileUtils;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class HaxeSemanticAnnotator implements Annotator {
@Override
public void annotate(PsiElement element, AnnotationHolder holder) {
analyzeSingle(element, holder);
}
static void analyzeSingle(final PsiElement element, AnnotationHolder holder) {
if (element instanceof HaxePackageStatement) {
PackageChecker.check((HaxePackageStatement)element, holder);
}
else if (element instanceof HaxeMethod) {
MethodChecker.check((HaxeMethod)element, holder);
}
else if (element instanceof HaxeClass) {
ClassChecker.check((HaxeClass)element, holder);
}
else if (element instanceof HaxeType) {
TypeChecker.check((HaxeType)element, holder);
}
else if (element instanceof HaxeVarDeclaration) {
FieldChecker.check((HaxeVarDeclaration)element, holder);
}
}
}
class TypeTagChecker {
public static void check(
final PsiElement erroredElement,
final HaxeTypeTag tag,
final HaxeVarInit initExpression,
boolean requireConstant,
final AnnotationHolder holder
) {
final ResultHolder type1 = HaxeTypeResolver.getTypeFromTypeTag(tag, erroredElement);
final ResultHolder type2 = getTypeFromVarInit(initExpression);
final HaxeDocumentModel document = HaxeDocumentModel.fromElement(tag);
if (!type1.canAssign(type2)) {
// @TODO: Move to bundle
Annotation annotation =
holder.createErrorAnnotation(erroredElement, "Incompatible type " + type1 + " can't be assigned from " + type2);
annotation.registerFix(new HaxeFixer("Change type") {
@Override
public void run() {
document.replaceElementText(tag, ":" + type2.toStringWithoutConstant());
}
});
annotation.registerFix(new HaxeFixer("Remove init") {
@Override
public void run() {
document.replaceElementText(initExpression, "", StripSpaces.BEFORE);
}
});
}
else if (requireConstant && type2.getType().getConstant() == null) {
// TODO: Move to bundle
holder.createErrorAnnotation(erroredElement, "Parameter default type should be constant but was " + type2);
}
}
@NotNull
static ResultHolder getTypeFromVarInit(HaxeVarInit init) {
final ResultHolder abstractEnumFieldInitType = HaxeAbstractEnumUtil.getStaticMemberExpression(init.getExpression());
if(abstractEnumFieldInitType != null) {
return abstractEnumFieldInitType;
}
// fallback to simple init expression
return HaxeTypeResolver.getPsiElementType(init);
}
}
class FieldChecker {
public static void check(final HaxeVarDeclaration var, final AnnotationHolder holder) {
HaxeFieldModel field = new HaxeFieldModel(var);
if (field.isProperty()) {
checkProperty(field, holder);
}
if (field.hasInitializer() && field.hasTypeTag()) {
TypeTagChecker.check(field.getPsi(), field.getTypeTagPsi(), field.getInitializerPsi(), false, holder);
}
// Checking for variable redefinition.
HashSet<HaxeClassModel> classSet = new HashSet<HaxeClassModel>();
HaxeClassModel fieldDeclaringClass = field.getDeclaringClass();
classSet.add(fieldDeclaringClass);
while (fieldDeclaringClass != null) {
fieldDeclaringClass = fieldDeclaringClass.getParentClass();
if (classSet.contains(fieldDeclaringClass)) {
break;
} else {
classSet.add(fieldDeclaringClass);
}
if (fieldDeclaringClass != null) {
for (HaxeFieldModel parentField : fieldDeclaringClass.getFields()) {
if (parentField.getName().equals(field.getName())) {
if (parentField.isStatic()) {
holder.createWarningAnnotation(field.getNameOrBasePsi(), "Field '" + field.getName()
+ "' overrides a static field of a superclass.");
} else {
holder.createErrorAnnotation(field.getDeclarationPsi(), "Redefinition of variable '" + field.getName()
+ "' in subclass is not allowed. Previously declared at '" + fieldDeclaringClass.getName() + "'.");
}
break;
}
}
}
}
}
public static void checkProperty(final HaxeFieldModel field, final AnnotationHolder holder) {
final HaxeDocumentModel document = field.getDocument();
if (field.getGetterPsi() != null && !field.getGetterType().isValidGetter()) {
holder.createErrorAnnotation(field.getGetterPsi(), "Invalid getter accessor");
}
if (field.getSetterPsi() != null && !field.getSetterType().isValidSetter()) {
holder.createErrorAnnotation(field.getSetterPsi(), "Invalid setter accessor");
}
checkPropertyAccessorMethods(field, holder);
if (field.isProperty() && !field.isRealVar() && field.hasInitializer()) {
final HaxeVarInit psi = field.getInitializerPsi();
Annotation annotation = holder.createErrorAnnotation(
field.getInitializerPsi(),
"This field cannot be initialized because it is not a real variable"
);
annotation.registerFix(new HaxeFixer("Remove init") {
@Override
public void run() {
document.replaceElementText(psi, "", StripSpaces.BEFORE);
}
});
annotation.registerFix(new HaxeFixer("Add @:isVar") {
@Override
public void run() {
field.getModifiers().addModifier(HaxeModifierType.IS_VAR);
}
});
if (field.getSetterPsi() != null) {
annotation.registerFix(new HaxeFixer("Make setter null") {
@Override
public void run() {
document.replaceElementText(field.getSetterPsi(), "null");
}
});
}
}
}
static void checkPropertyAccessorMethods(final HaxeFieldModel field, final AnnotationHolder holder) {
if (field.getDeclaringClass().isInterface()) {
return;
}
if (field.getGetterType() == HaxeAccessorType.GET) {
final String methodName = "get_" + field.getName();
HaxeMethodModel method = field.getDeclaringClass().getMethod(methodName);
if (method == null) {
Annotation annotation = holder.createErrorAnnotation(field.getGetterPsi(), "Can't find method " + methodName);
annotation.registerFix(new HaxeFixer("Add method") {
@Override
public void run() {
field.getDeclaringClass().addMethod(methodName);
}
});
}
}
if (field.getSetterType() == HaxeAccessorType.SET) {
final String methodName = "set_" + field.getName();
HaxeMethodModel method = field.getDeclaringClass().getMethod(methodName);
if (method == null) {
Annotation annotation = holder.createErrorAnnotation(field.getSetterPsi(), "Can't find method " + methodName);
annotation.registerFix(new HaxeFixer("Add method") {
@Override
public void run() {
field.getDeclaringClass().addMethod(methodName);
}
});
}
}
}
}
class TypeChecker {
static public void check(final HaxeType type, final AnnotationHolder holder) {
if (true) {
// HACK - Find the identifier manually, rather than just getting it from the reference.
// There is an error in the BNF that maps a number of reference types to HaxeReferenceExpression
// even though the types do not necessarily have one. If an identifier doesn't exist,
// HaxeReferenceExpression.getIdentifier() will throw a NotNull exception; searching the
// children doesn't.
HaxeReferenceExpression expression = type.getReferenceExpression();
HaxeIdentifier identifier = PsiTreeUtil.getChildOfType(expression, HaxeIdentifier.class);
check(identifier, holder);
} else {
check(type.getReferenceExpression().getIdentifier(), holder);
}
}
static public void check(final PsiIdentifier identifier, final AnnotationHolder holder) {
if (identifier == null) return;
final String typeName = identifier.getText();
if (!HaxeClassModel.isValidClassName(typeName)) {
Annotation annotation = holder.createErrorAnnotation(identifier, "Type name must start by upper case");
annotation.registerFix(new HaxeFixer("Change name") {
@Override
public void run() {
HaxeDocumentModel.fromElement(identifier).replaceElementText(
identifier,
typeName.substring(0, 1).toUpperCase() + typeName.substring(1)
);
}
});
}
}
}
class ClassChecker {
static public void check(final HaxeClass clazzPsi, final AnnotationHolder holder) {
HaxeClassModel clazz = clazzPsi.getModel();
checkDuplicatedFields(clazz, holder);
checkClassName(clazz, holder);
checkInterfaces(clazz, holder);
checkExtends(clazz, holder);
checkInterfacesMethods(clazz, holder);
}
static private void checkDuplicatedFields(final HaxeClassModel clazz, final AnnotationHolder holder) {
Map<String, HaxeMemberModel> map = new HashMap<String, HaxeMemberModel>();
Set<HaxeMemberModel> repeatedMembers = new HashSet<HaxeMemberModel>();
for (HaxeMemberModel member : clazz.getMembersSelf()) {
final String memberName = member.getName();
HaxeMemberModel repeatedMember = map.get(memberName);
if (repeatedMember != null) {
repeatedMembers.add(member);
repeatedMembers.add(repeatedMember);
}
else {
map.put(memberName, member);
}
}
for (HaxeMemberModel member : repeatedMembers) {
holder.createErrorAnnotation(member.getNameOrBasePsi(), "Duplicate class field declaration : " + member.getName());
}
//Duplicate class field declaration
}
static private void checkClassName(final HaxeClassModel clazz, final AnnotationHolder holder) {
TypeChecker.check(clazz.getNamePsi(), holder);
}
static public void checkExtends(final HaxeClassModel clazz, final AnnotationHolder holder) {
HaxeClassReferenceModel reference = clazz.getParentClassReference();
if (reference != null) {
HaxeClassModel aClass1 = reference.getHaxeClass();
if (aClass1 != null) {
if(isAnonymousType(clazz)) {
if(!isAnonymousType(aClass1)) {
// @TODO: Move to bundle
holder.createErrorAnnotation(reference.getPsi(), "Not an anonymous type");
}
}
else if(clazz.isInterface()) {
if(!aClass1.isInterface()) {
// @TODO: Move to bundle
holder.createErrorAnnotation(reference.getPsi(), "Not an interface");
}
}
else if(!aClass1.isClass()) {
// @TODO: Move to bundle
holder.createErrorAnnotation(reference.getPsi(), "Not a class");
}
final String qname1 = aClass1.haxeClass.getQualifiedName();
final String qname2 = clazz.haxeClass.getQualifiedName();
if(qname1.equals(qname2)) {
// @TODO: Move to bundle
holder.createErrorAnnotation(reference.getPsi(), "Cannot extend self");
}
}
}
}
static private boolean isAnonymousType(HaxeClassModel clazz) {
if(clazz != null && clazz.haxeClass != null) {
HaxeClass haxeClass = clazz.haxeClass;
if(haxeClass instanceof HaxeAnonymousType) {
return true;
}
if(haxeClass instanceof HaxeTypedefDeclaration) {
HaxeTypeOrAnonymous anonOrType = ((HaxeTypedefDeclaration)haxeClass).getTypeOrAnonymous();
if(anonOrType != null) {
return anonOrType.getAnonymousType() != null;
}
}
}
return false;
}
static public void checkInterfaces(final HaxeClassModel clazz, final AnnotationHolder holder) {
for (HaxeClassReferenceModel interfaze : clazz.getImplementingInterfaces()) {
if (interfaze.getHaxeClass() == null || !interfaze.getHaxeClass().isInterface()) {
// @TODO: Move to bundle
holder.createErrorAnnotation(interfaze.getPsi(), "Not an interface");
}
}
}
static public void checkInterfacesMethods(final HaxeClassModel clazz, final AnnotationHolder holder) {
for (HaxeClassReferenceModel reference : clazz.getImplementingInterfaces()) {
checkInterfaceMethods(clazz, reference, holder);
}
}
static public void checkInterfaceMethods(
final HaxeClassModel clazz,
final HaxeClassReferenceModel intReference,
final AnnotationHolder holder
) {
final List<HaxeMethodModel> missingMethods = new ArrayList<HaxeMethodModel>();
final List<String> missingMethodsNames = new ArrayList<String>();
if (intReference.getHaxeClass() != null) {
for (HaxeMethodModel intMethod : intReference.getHaxeClass().getMethods()) {
if (!intMethod.isStatic()) {
HaxeMethodModel selfMethod = clazz.getMethodSelf(intMethod.getName());
if (selfMethod == null) {
missingMethods.add(intMethod);
missingMethodsNames.add(intMethod.getName());
}
else {
MethodChecker.checkMethodsSignatureCompatibility(selfMethod, intMethod, holder);
}
}
}
}
if (missingMethods.size() > 0) {
// @TODO: Move to bundle
Annotation annotation = holder.createErrorAnnotation(
intReference.getPsi(),
"Not implemented methods: " + StringUtils.join(missingMethodsNames, ", ")
);
annotation.registerFix(new HaxeFixer("Implement methods") {
@Override
public void run() {
clazz.addMethodsFromPrototype(missingMethods);
}
});
}
}
}
class MethodChecker {
static public void check(final HaxeMethod methodPsi, final AnnotationHolder holder) {
final HaxeMethodModel currentMethod = methodPsi.getModel();
checkTypeTagInInterfacesAndExternClass(currentMethod, holder);
checkMethodArguments(currentMethod, holder);
checkOverride(methodPsi, holder);
if (HaxeSemanticAnnotatorConfig.ENABLE_EXPERIMENTAL_BODY_CHECK) {
MethodBodyChecker.check(methodPsi, holder);
}
//currentMethod.getBodyPsi()
}
static public void checkTypeTagInInterfacesAndExternClass(final HaxeMethodModel currentMethod, final AnnotationHolder holder) {
HaxeClassModel currentClass = currentMethod.getDeclaringClass();
if (currentClass.isExtern() || currentClass.isInterface()) {
if (currentMethod.getReturnTypeTagPsi() == null) {
holder.createErrorAnnotation(currentMethod.getNameOrBasePsi(), HaxeBundle.message("haxe.semantic.type.required"));
}
for (final HaxeParameterModel param : currentMethod.getParameters()) {
if (param.getTypeTagPsi() == null) {
holder.createErrorAnnotation(param.getNameOrBasePsi(), HaxeBundle.message("haxe.semantic.type.required"));
}
}
}
}
static public void checkMethodArguments(final HaxeMethodModel currentMethod, final AnnotationHolder holder) {
boolean hasOptional = false;
HashMap<String, PsiElement> argumentNames = new HashMap<String, PsiElement>();
for (final HaxeParameterModel param : currentMethod.getParameters()) {
String paramName = param.getName();
if (param.hasOptionalPsi() && param.getVarInitPsi() != null) {
// @TODO: Move to bundle
holder.createWarningAnnotation(param.getOptionalPsi(), "Optional not needed when specified an init value");
}
if (param.getVarInitPsi() != null && param.getTypeTagPsi() != null) {
TypeTagChecker.check(
param.getPsi(),
param.getTypeTagPsi(),
param.getVarInitPsi(),
true,
holder
);
}
if (param.isOptional()) {
hasOptional = true;
}
else if (hasOptional) {
// @TODO: Move to bundle
holder.createWarningAnnotation(param.getPsi(), "Non-optional argument after optional argument");
}
if (argumentNames.containsKey(paramName)) {
// @TODO: Move to bundle
holder.createWarningAnnotation(param.getNameOrBasePsi(), "Repeated argument name '" + paramName + "'");
holder.createWarningAnnotation(argumentNames.get(paramName), "Repeated argument name '" + paramName + "'");
}
else {
argumentNames.put(paramName, param.getNameOrBasePsi());
}
}
}
static public void checkOverride(final HaxeMethod methodPsi, final AnnotationHolder holder) {
final HaxeMethodModel currentMethod = methodPsi.getModel();
final HaxeClassModel currentClass = currentMethod.getDeclaringClass();
final HaxeModifiersModel currentModifiers = currentMethod.getModifiers();
final HaxeClassReferenceModel parentClass = (currentClass != null) ? currentClass.getParentClassReference() : null;
final HaxeMethodModel parentMethod =
((parentClass != null) && parentClass.getHaxeClass() != null) ? parentClass.getHaxeClass().getMethod(currentMethod.getName()) : null;
final HaxeModifiersModel parentModifiers = (parentMethod != null) ? parentMethod.getModifiers() : null;
boolean requiredOverride = false;
if (currentMethod.isConstructor()) {
requiredOverride = false;
if (currentModifiers.hasModifier(HaxeModifierType.STATIC)) {
// @TODO: Move to bundle
holder.createErrorAnnotation(currentMethod.getNameOrBasePsi(), "Constructor can't be static").registerFix(
new HaxeModifierRemoveFixer(currentModifiers, HaxeModifierType.STATIC)
);
}
}
else if (currentMethod.isStaticInit()) {
requiredOverride = false;
if (!currentModifiers.hasModifier(HaxeModifierType.STATIC)) {
holder.createErrorAnnotation(currentMethod.getNameOrBasePsi(), "__init__ must be static").registerFix(
new HaxeModifierAddFixer(currentModifiers, HaxeModifierType.STATIC)
);
}
}
else if (parentMethod != null) {
if (parentMethod.isStatic()) {
holder.createWarningAnnotation(currentMethod.getNameOrBasePsi(), "Method '" + currentMethod.getName()
+ "' overrides a static method of a superclass");
}
else {
requiredOverride = true;
if (parentModifiers.hasAnyModifier(HaxeModifierType.INLINE, HaxeModifierType.STATIC, HaxeModifierType.FINAL)) {
Annotation annotation =
holder.createErrorAnnotation(currentMethod.getNameOrBasePsi(), "Can't override static, inline or final methods");
for (HaxeModifierType mod : new HaxeModifierType[]{HaxeModifierType.FINAL, HaxeModifierType.INLINE, HaxeModifierType.STATIC}) {
if (parentModifiers.hasModifier(mod)) {
annotation.registerFix(
new HaxeModifierRemoveFixer(parentModifiers, mod, "Remove " + mod.s + " from " + parentMethod.getFullName())
);
}
}
}
if (currentModifiers.getVisibility().hasLowerVisibilityThan(parentModifiers.getVisibility())) {
Annotation annotation = holder.createErrorAnnotation(
currentMethod.getNameOrBasePsi(),
"Field " +
currentMethod.getName() +
" has less visibility (public/private) than superclass one"
);
annotation.registerFix(
new HaxeModifierReplaceVisibilityFixer(currentModifiers, parentModifiers.getVisibility(), "Change current method visibility"));
annotation.registerFix(
new HaxeModifierReplaceVisibilityFixer(parentModifiers, currentModifiers.getVisibility(), "Change parent method visibility"));
}
}
}
//System.out.println(aClass);
if (currentModifiers.hasModifier(HaxeModifierType.OVERRIDE) && !requiredOverride) {
holder.createErrorAnnotation(currentModifiers.getModifierPsi(HaxeModifierType.OVERRIDE), "Overriding nothing").registerFix(
new HaxeModifierRemoveFixer(currentModifiers, HaxeModifierType.OVERRIDE)
);
}
else if (requiredOverride) {
if (!currentModifiers.hasModifier(HaxeModifierType.OVERRIDE)) {
holder.createErrorAnnotation(currentMethod.getNameOrBasePsi(), "Must override").registerFix(
new HaxeModifierAddFixer(currentModifiers, HaxeModifierType.OVERRIDE)
);
}
else {
// It is rightly overriden. Now check the signature.
checkMethodsSignatureCompatibility(currentMethod, parentMethod, holder);
}
}
}
static public void checkMethodsSignatureCompatibility(
@NotNull final HaxeMethodModel currentMethod,
@NotNull final HaxeMethodModel parentMethod,
final AnnotationHolder holder
) {
final HaxeDocumentModel document = currentMethod.getDocument();
List<HaxeParameterModel> currentParameters = currentMethod.getParameters();
final List<HaxeParameterModel> parentParameters = parentMethod.getParameters();
int minParameters = Math.min(currentParameters.size(), parentParameters.size());
if (currentParameters.size() > parentParameters.size()) {
for (int n = minParameters; n < currentParameters.size(); n++) {
final HaxeParameterModel currentParam = currentParameters.get(n);
holder.createErrorAnnotation(currentParam.getPsi(), "Unexpected argument").registerFix(
new HaxeFixer("Remove argument") {
@Override
public void run() {
currentParam.remove();
}
});
}
}
else if (currentParameters.size() != parentParameters.size()) {
holder.createErrorAnnotation(
currentMethod.getNameOrBasePsi(),
"Not matching arity expected " +
parentParameters.size() +
" arguments but found " +
currentParameters.size()
);
}
for (int n = 0; n < minParameters; n++) {
final HaxeParameterModel currentParam = currentParameters.get(n);
final HaxeParameterModel parentParam = parentParameters.get(n);
if (!HaxeTypeCompatible.canAssignToFrom(currentParam.getType(), parentParam.getType())) {
holder.createErrorAnnotation(
currentParam.getPsi(),
"Type " + currentParam.getType() + " is not compatible with " + parentParam.getType()).registerFix
(
new HaxeFixer("Change type") {
@Override
public void run() {
document.replaceElementText(currentParam.getTypeTagPsi(), parentParam.getTypeTagPsi().getText());
}
}
)
;
}
}
ResultHolder currentResult = currentMethod.getResultType();
ResultHolder parentResult = parentMethod.getResultType();
if (!currentResult.canAssign(parentResult)) {
PsiElement psi = currentMethod.getReturnTypeTagOrNameOrBasePsi();
holder.createErrorAnnotation(psi, "Not compatible return type " + currentResult + " != " + parentResult);
}
}
}
class PackageChecker {
static public void check(final HaxePackageStatement element, final AnnotationHolder holder) {
final HaxeReferenceExpression expression = element.getReferenceExpression();
String packageName = (expression != null) ? expression.getText() : "";
PsiDirectory fileDirectory = element.getContainingFile().getParent();
List<PsiFileSystemItem> fileRange = PsiFileUtils.getRange(PsiFileUtils.findRoot(fileDirectory), fileDirectory);
fileRange.remove(0);
String actualPath = PsiFileUtils.getListPath(fileRange);
final String actualPackage = actualPath.replace('/', '.');
final String actualPackage2 = HaxeResolveUtil.getPackageName(element.getContainingFile());
// @TODO: Should use HaxeResolveUtil
for (String s : StringUtils.split(packageName, '.')) {
if (!s.substring(0, 1).toLowerCase().equals(s.substring(0, 1))) {
//HaxeSemanticError.addError(element, new HaxeSemanticError("Package name '" + s + "' must start with a lower case character"));
// @TODO: Move to bundle
holder.createErrorAnnotation(element, "Package name '" + s + "' must start with a lower case character");
}
}
if (!packageName.equals(actualPackage)) {
holder.createErrorAnnotation(
element,
"Invalid package name! '" + packageName + "' should be '" + actualPackage + "'").registerFix(
new HaxeFixer("Fix package") {
@Override
public void run() {
Document document =
PsiDocumentManager.getInstance(element.getProject()).getDocument(element.getContainingFile());
if (expression != null) {
TextRange range = expression.getTextRange();
document.replaceString(range.getStartOffset(), range.getEndOffset(), actualPackage);
}
else {
int offset =
element.getNode().findChildByType(HaxeTokenTypes.OSEMI).getTextRange().getStartOffset();
document.replaceString(offset, offset, actualPackage);
}
}
}
);
}
}
}
class MethodBodyChecker {
public static void check(HaxeMethod psi, AnnotationHolder holder) {
final HaxeMethodModel method = psi.getModel();
HaxeTypeResolver.getPsiElementType(method.getBodyPsi(), holder);
}
}