package org.eclipse.dltk.internal.javascript.parser; import static org.eclipse.dltk.internal.javascript.validation.JavaScriptValidations.reportValidationStatus; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import org.eclipse.core.runtime.Assert; import org.eclipse.dltk.annotations.Internal; import org.eclipse.dltk.annotations.Nullable; import org.eclipse.dltk.compiler.problem.IProblemIdentifier; import org.eclipse.dltk.compiler.problem.IValidationStatus; import org.eclipse.dltk.compiler.problem.ValidationMultiStatus; import org.eclipse.dltk.compiler.problem.ValidationStatus; import org.eclipse.dltk.core.ISourceNode; import org.eclipse.dltk.internal.javascript.ti.IReferenceAttributes; import org.eclipse.dltk.internal.javascript.validation.ValidationMessages; import org.eclipse.dltk.javascript.core.JavaScriptProblems; import org.eclipse.dltk.javascript.parser.JSProblemReporter; import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTag; import org.eclipse.dltk.javascript.typeinference.IValueCollection; import org.eclipse.dltk.javascript.typeinference.IValueReference; import org.eclipse.dltk.javascript.typeinference.ReferenceKind; import org.eclipse.dltk.javascript.typeinfo.IRType; import org.eclipse.dltk.javascript.typeinfo.ITypeCheck; import org.eclipse.dltk.javascript.typeinfo.ITypeChecker; import org.eclipse.dltk.javascript.typeinfo.ITypeCheckerExtension; import org.eclipse.dltk.javascript.typeinfo.ITypeInfoContext; import org.eclipse.dltk.javascript.typeinfo.RTypes; import org.eclipse.dltk.javascript.typeinfo.TypeUtil; import org.eclipse.dltk.javascript.typeinfo.model.AnyType; import org.eclipse.dltk.javascript.typeinfo.model.ArrayType; import org.eclipse.dltk.javascript.typeinfo.model.ClassType; import org.eclipse.dltk.javascript.typeinfo.model.FunctionType; import org.eclipse.dltk.javascript.typeinfo.model.GenericType; import org.eclipse.dltk.javascript.typeinfo.model.JSType; import org.eclipse.dltk.javascript.typeinfo.model.MapType; import org.eclipse.dltk.javascript.typeinfo.model.Member; import org.eclipse.dltk.javascript.typeinfo.model.Parameter; import org.eclipse.dltk.javascript.typeinfo.model.ParameterizedType; import org.eclipse.dltk.javascript.typeinfo.model.RecordType; import org.eclipse.dltk.javascript.typeinfo.model.SimpleType; import org.eclipse.dltk.javascript.typeinfo.model.Type; import org.eclipse.dltk.javascript.typeinfo.model.TypeKind; import org.eclipse.dltk.javascript.typeinfo.model.TypeVariable; import org.eclipse.dltk.javascript.typeinfo.model.UndefinedType; import org.eclipse.dltk.javascript.typeinfo.model.UnionType; import org.eclipse.dltk.javascript.validation.IValidatorExtension; import org.eclipse.dltk.javascript.validation.IValidatorExtension2; import org.eclipse.emf.common.util.EList; import org.eclipse.osgi.util.NLS; public class JSDocValidatorFactory { public static abstract class AbstractTypeChecker implements ITypeChecker { private int defaults = DEFAULT; public int getDefaults() { return defaults; } public void setDefaults(int defaults) { this.defaults = defaults; } public void checkType(JSType type, ISourceNode node) { checkType(type, node, getDefaults()); } public void checkType(JSType type, ISourceNode tag, int flags) { if (type == null) { return; } if (type instanceof UnionType) { for (JSType targetType : ((UnionType) type).getTargets()) { checkType(targetType, tag, flags); } } else if (type instanceof RecordType) { for (Member member : ((RecordType) type).getMembers()) { checkType(member.getType(), tag, flags); } } else if (type instanceof FunctionType) { final FunctionType func = (FunctionType) type; checkType(((FunctionType) type).getReturnType(), tag, flags); for (Parameter parameter : func.getParameters()) { checkType(parameter.getType(), tag, flags); } } else if (type instanceof AnyType || type instanceof UndefinedType) { // OK } else if (type instanceof ArrayType) { checkType(((ArrayType) type).getItemType(), tag, flags); } else if (type instanceof MapType) { checkType(((MapType) type).getKeyType(), tag, flags); checkType(((MapType) type).getValueType(), tag, flags); } else if (type instanceof SimpleType) { if (type instanceof ParameterizedType) { final ParameterizedType parameterized = (ParameterizedType) type; for (JSType param : parameterized.getActualTypeArguments()) { checkType(param, tag, flags); } checkType(((SimpleType) type).getTarget(), tag, flags, new ITypeCheck[] { new ParameterizedTypeCheck( parameterized) }); } else { checkType(((SimpleType) type).getTarget(), tag, flags, null); } } else if (type instanceof ClassType) { final Type t = ((ClassType) type).getTarget(); if (t == null) { return; } checkType(t, tag, flags, null); } } public abstract void checkType(Type type, ISourceNode tag, int flags, @Nullable ITypeCheck[] checks); } private static class ParameterizedTypeCheck implements ITypeCheck { private final ParameterizedType parameterizedType; public ParameterizedTypeCheck(ParameterizedType parameterizedType) { this.parameterizedType = parameterizedType; } public IValidationStatus checkType(ITypeInfoContext context, Type type) { if (type instanceof GenericType) { final GenericType genericType = (GenericType) type; final EList<TypeVariable> typeVariables = genericType .getTypeParameters(); final int typeVariableCount = typeVariables.size(); if (typeVariableCount != parameterizedType .getActualTypeArguments().size()) { return new ValidationStatus( JavaScriptProblems.PARAMETERIZED_TYPE_INCORRECT_ARGUMENTS, NLS.bind( ValidationMessages.IncorrectNumberOfTypeArguments, type.getName())); } List<ValidationStatus> statuses = null; for (int i = 0; i < typeVariableCount; ++i) { final TypeVariable variable = typeVariables.get(i); if (variable.getBound() != null) { final IRType bound = RTypes.create(context, variable.getBound()); final IRType actual = context .contextualize(parameterizedType .getActualTypeArguments().get(i)); if (!bound.isAssignableFrom(actual).ok()) { if (statuses == null) { statuses = new ArrayList<ValidationStatus>(); } statuses.add(new ValidationStatus( JavaScriptProblems.PARAMETERIZED_TYPE_INCORRECT_ARGUMENTS, NLS.bind( ValidationMessages.ParameterizedBoundMismatch, new Object[] { actual, variable.getName(), bound, genericType.getName() }))); } } } return ValidationMultiStatus.of(statuses); } else { return new ValidationStatus( JavaScriptProblems.NOT_GENERIC_TYPE, NLS.bind( ValidationMessages.NotGenericType, type.getName())); } } } public static class TypeChecker extends AbstractTypeChecker implements ITypeCheckerExtension { private final List<QueueItem> queue = new ArrayList<QueueItem>(); private final ITypeInfoContext context; private final JSProblemReporter reporter; public TypeChecker(ITypeInfoContext context, JSProblemReporter reporter) { this.context = context; this.reporter = reporter; } @Override public void checkType(JSType type, ISourceNode tag, int flags) { if (extensions != null) { for (IValidatorExtension extension : extensions) { if (extension instanceof IValidatorExtension2) { final IValidationStatus status = ((IValidatorExtension2) extension) .validateTypeExpression(type); if (status != null && status != ValidationStatus.OK) { reportValidationStatus(reporter, status, tag, JavaScriptProblems.INACCESSIBLE_TYPE, ValidationMessages.InaccessibleType, type.getName()); return; } } } } super.checkType(type, tag, flags); } @Override public void checkType(Type type, ISourceNode tag, int flags, @Nullable ITypeCheck[] checks) { final TypeKind kind = type.getKind(); if (kind == TypeKind.UNKNOWN) { queue.add(new QueueItem(type, tag, context.currentCollection(), flags, checks)); } else if (kind == TypeKind.UNRESOLVED) { final Type resolved = context.resolveType(type); if (resolved != type && resolved.getKind() != TypeKind.UNKNOWN) { checkDeprecatedType(resolved, tag); if (checks != null) { doChecks(resolved, tag, checks); } } else { queue.add(new QueueItem(type, tag, context .currentCollection(), flags, checks)); } } else { checkDeprecatedType(type, tag); if (checks != null) { doChecks(type, tag, checks); } } } public void validate() { for (QueueItem item : queue) { doCheckType(context.resolveType(item.type), item.tag, item.flags, item.checks, item.collection); } } private void doCheckType(Type type, ISourceNode tag, int flags, @Nullable ITypeCheck[] checks, IValueCollection collection) { if (type.eIsProxy()) { Assert.isTrue(!type.eIsProxy(), "Type \"" + type.getName() + "\" is a proxy"); } if (type.getKind() == TypeKind.UNKNOWN) { if ((flags & LOCAL_TYPES) != 0 && collection != null) { if (collection.getChild(type.getName()).exists()) { return; } // if it still is not found, test if it is a // "package type" // an try to resolve that to a existing child. String className = type.getName(); if (className.indexOf('.') != -1) { StringTokenizer st = new StringTokenizer(className, "."); IValueReference child = null; while (st.hasMoreTokens()) { String token = st.nextToken(); if (child == null) child = collection.getChild(token); else { if (child.getKind() == ReferenceKind.FUNCTION) { IValueCollection function = (IValueCollection) child .getAttribute(IReferenceAttributes.FUNCTION_SCOPE); if (function != null) child = function.getThis(); } child = child.getChild(token); } if (!child.exists()) { child = null; break; } } // if the child is not null, it was found. if (child != null) return; } } reportUnknownType(tag, TypeUtil.getName(type)); } else { checkDeprecatedType(type, tag); if (checks != null) { doChecks(type, tag, checks); } } } private void doChecks(Type type, ISourceNode tag, ITypeCheck[] checks) { for (ITypeCheck check : checks) { final IValidationStatus status = check.checkType(context, type); if (status != null && status != ValidationStatus.OK) { reportValidationStatus(reporter, status, tag, JavaScriptProblems.INACCESSIBLE_TYPE, ValidationMessages.InaccessibleType, type.getName()); } } } private void checkDeprecatedType(Type type, ISourceNode tag) { if (type.isDeprecated()) { reporter.reportProblem( JavaScriptProblems.DEPRECATED_TYPE, NLS.bind(ValidationMessages.DeprecatedType, TypeUtil.getName(type)), tag.start(), tag.end()); return; } else if (extensions != null) { for (IValidatorExtension extension : extensions) { if (extension instanceof IValidatorExtension2) { final IValidationStatus status = ((IValidatorExtension2) extension) .validateAccessibility(type); if (status != null && status != ValidationStatus.OK) { reportValidationStatus(reporter, status, tag, JavaScriptProblems.INACCESSIBLE_TYPE, ValidationMessages.InaccessibleType, type.getName()); return; } } } } } public void reportUnknownType(ISourceNode tag, String name) { reportUnknownType(JavaScriptProblems.UNKNOWN_TYPE, tag, name); } public void reportUnknownType(IProblemIdentifier identifier, ISourceNode node, String name) { int start; int end; if (node instanceof JSDocTag) { final JSDocTag tag = (JSDocTag) node; end = tag.end(); start = end - tag.value().length(); int index = tag.value().indexOf('{'); if (index != -1) { int index2 = tag.value().indexOf('}', index); if (index2 != -1) { start = start + index + 1; end = start + index2 - 1; } } } else { start = node.start(); end = node.end(); } reporter.reportProblem(identifier, NLS.bind(ValidationMessages.UnknownType, name), start, end); } private IValidatorExtension[] extensions; public void setExtensions(IValidatorExtension[] extensions) { this.extensions = extensions; } } private static class QueueItem { final Type type; final ISourceNode tag; final IValueCollection collection; final int flags; @Nullable final ITypeCheck[] checks; public QueueItem(Type type, ISourceNode tag, IValueCollection collection, int flags, @Nullable ITypeCheck[] checks) { this.type = type; this.tag = tag; this.collection = collection; this.flags = flags; this.checks = checks; } } @Internal static class NopTypeChecker implements ITypeChecker { public int getDefaults() { return DEFAULT; } public void setDefaults(int flags) { } public void checkType(JSType type, ISourceNode node) { } public void checkType(JSType type, ISourceNode node, int flags) { } public void checkType(Type type, ISourceNode node, int flags, ITypeCheck[] checks) { } } public static ITypeChecker createNopTypeChecker() { return new NopTypeChecker(); } }