| /******************************************************************************* |
| * Copyright (c) 2010 xored software, Inc. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * xored software, Inc. - initial API and Implementation (Alex Panchenko) |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.javascript.validation; |
| |
| import static org.eclipse.dltk.internal.javascript.ti.IReferenceAttributes.PHANTOM; |
| import static org.eclipse.dltk.internal.javascript.ti.IReferenceAttributes.R_METHOD; |
| import static org.eclipse.dltk.internal.javascript.validation.JavaScriptValidations.typeOf; |
| import static org.eclipse.dltk.javascript.typeinfo.RUtils.locationOf; |
| import static org.eclipse.osgi.util.NLS.bind; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.dltk.annotations.NonNull; |
| import org.eclipse.dltk.annotations.Nullable; |
| import org.eclipse.dltk.ast.ASTNode; |
| 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.core.builder.IBuildContext; |
| import org.eclipse.dltk.core.builder.IBuildParticipant; |
| import org.eclipse.dltk.core.builder.IBuildParticipantExtension; |
| import org.eclipse.dltk.core.builder.IBuildParticipantExtension4; |
| import org.eclipse.dltk.internal.javascript.parser.JSDocValidatorFactory.TypeChecker; |
| import org.eclipse.dltk.internal.javascript.ti.ConstantValue; |
| import org.eclipse.dltk.internal.javascript.ti.IReferenceAttributes; |
| import org.eclipse.dltk.internal.javascript.ti.ITypeInferenceContext; |
| import org.eclipse.dltk.internal.javascript.ti.IValue; |
| import org.eclipse.dltk.internal.javascript.ti.JSMethod; |
| import org.eclipse.dltk.internal.javascript.ti.TypeInferencer2; |
| import org.eclipse.dltk.internal.javascript.ti.TypeInferencerVisitor; |
| import org.eclipse.dltk.javascript.ast.Argument; |
| import org.eclipse.dltk.javascript.ast.BinaryOperation; |
| import org.eclipse.dltk.javascript.ast.CallExpression; |
| import org.eclipse.dltk.javascript.ast.Expression; |
| import org.eclipse.dltk.javascript.ast.FunctionStatement; |
| import org.eclipse.dltk.javascript.ast.GetArrayItemExpression; |
| import org.eclipse.dltk.javascript.ast.Identifier; |
| import org.eclipse.dltk.javascript.ast.IfStatement; |
| import org.eclipse.dltk.javascript.ast.JSNode; |
| import org.eclipse.dltk.javascript.ast.NewExpression; |
| import org.eclipse.dltk.javascript.ast.PropertyExpression; |
| import org.eclipse.dltk.javascript.ast.ReturnStatement; |
| import org.eclipse.dltk.javascript.ast.Script; |
| import org.eclipse.dltk.javascript.ast.StatementBlock; |
| import org.eclipse.dltk.javascript.ast.ThrowStatement; |
| import org.eclipse.dltk.javascript.ast.UnaryOperation; |
| import org.eclipse.dltk.javascript.ast.VariableDeclaration; |
| import org.eclipse.dltk.javascript.ast.VariableStatement; |
| import org.eclipse.dltk.javascript.core.JSBindings; |
| import org.eclipse.dltk.javascript.core.JavaScriptProblems; |
| import org.eclipse.dltk.javascript.internal.core.TemporaryBindings; |
| import org.eclipse.dltk.javascript.internal.core.ThreadTypeSystemImpl; |
| import org.eclipse.dltk.javascript.parser.ISuppressWarningsState; |
| import org.eclipse.dltk.javascript.parser.JSParser; |
| import org.eclipse.dltk.javascript.parser.JSProblemReporter; |
| import org.eclipse.dltk.javascript.parser.PropertyExpressionUtils; |
| import org.eclipse.dltk.javascript.parser.Reporter; |
| import org.eclipse.dltk.javascript.typeinference.IAssignProtection; |
| import org.eclipse.dltk.javascript.typeinference.IAssignProtection2; |
| import org.eclipse.dltk.javascript.typeinference.IValueCollection; |
| import org.eclipse.dltk.javascript.typeinference.IValueReference; |
| import org.eclipse.dltk.javascript.typeinference.PhantomValueReference; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceKind; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceLocation; |
| import org.eclipse.dltk.javascript.typeinference.ValueReferenceUtil; |
| import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IVariable; |
| import org.eclipse.dltk.javascript.typeinfo.IRArrayType; |
| import org.eclipse.dltk.javascript.typeinfo.IRClassType; |
| import org.eclipse.dltk.javascript.typeinfo.IRConstructor; |
| import org.eclipse.dltk.javascript.typeinfo.IRElement; |
| import org.eclipse.dltk.javascript.typeinfo.IRFunctionType; |
| import org.eclipse.dltk.javascript.typeinfo.IRLocalType; |
| import org.eclipse.dltk.javascript.typeinfo.IRMapType; |
| import org.eclipse.dltk.javascript.typeinfo.IRMember; |
| import org.eclipse.dltk.javascript.typeinfo.IRMethod; |
| import org.eclipse.dltk.javascript.typeinfo.IRParameter; |
| import org.eclipse.dltk.javascript.typeinfo.IRProperty; |
| import org.eclipse.dltk.javascript.typeinfo.IRRecordMember; |
| import org.eclipse.dltk.javascript.typeinfo.IRRecordType; |
| import org.eclipse.dltk.javascript.typeinfo.IRType; |
| import org.eclipse.dltk.javascript.typeinfo.IRTypeDeclaration; |
| import org.eclipse.dltk.javascript.typeinfo.IRTypeExtension; |
| import org.eclipse.dltk.javascript.typeinfo.IRUnionType; |
| import org.eclipse.dltk.javascript.typeinfo.IRVariable; |
| 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.ITypeNames; |
| import org.eclipse.dltk.javascript.typeinfo.ITypeSystem; |
| import org.eclipse.dltk.javascript.typeinfo.JSTypeSet; |
| import org.eclipse.dltk.javascript.typeinfo.RTypes; |
| import org.eclipse.dltk.javascript.typeinfo.TypeCompatibility; |
| import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager; |
| import org.eclipse.dltk.javascript.typeinfo.TypeUtil; |
| import org.eclipse.dltk.javascript.typeinfo.model.Member; |
| import org.eclipse.dltk.javascript.typeinfo.model.ParameterKind; |
| import org.eclipse.dltk.javascript.typeinfo.model.Property; |
| import org.eclipse.dltk.javascript.typeinfo.model.Type; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeKind; |
| import org.eclipse.dltk.javascript.typeinfo.model.Visibility; |
| import org.eclipse.dltk.javascript.validation.IValidatorExtension; |
| import org.eclipse.osgi.util.NLS; |
| |
| public class TypeInfoValidator implements IBuildParticipant, |
| IBuildParticipantExtension, IBuildParticipantExtension4 { |
| |
| /** |
| * Public identifier of this build participant. |
| */ |
| public static final String ID = "org.eclipse.dltk.javascript.core.buildParticipant.typeinfo"; |
| |
| private boolean hasDependents; |
| |
| public boolean beginBuild(int buildType) { |
| return true; |
| } |
| |
| public void notifyDependents(IBuildParticipant[] dependents) { |
| hasDependents = true; |
| } |
| |
| private TypeInferencer2 inferencer; |
| |
| public void build(IBuildContext context) throws CoreException { |
| final Script script = JavaScriptValidations.parse(context); |
| if (script == null) { |
| return; |
| } |
| if (inferencer == null) { |
| inferencer = createTypeInferencer(); |
| } |
| inferencer.setModelElement(context.getSourceModule()); |
| inferencer.pushAttribute(ITypeInfoContext.BUILD_CONTEXT, context); |
| final JSProblemReporter reporter = JavaScriptValidations |
| .createReporter(context); |
| @SuppressWarnings("unchecked") |
| @Nullable |
| final Set<FunctionStatement> inconsistentReturns = (Set<FunctionStatement>) context |
| .get(JavaScriptValidations.ATTR_INCONSISTENT_RETURNS); |
| final ValidationVisitor visitor = new ValidationVisitor(inferencer, |
| reporter, inconsistentReturns, hasDependents); |
| inferencer.setVisitor(visitor); |
| inferencer.doInferencing(script); |
| if (hasDependents) { |
| inferencer.resetLocalState(); |
| context.set(TypeInfoValidator.ATTR_BINDINGS, visitor.bindings); |
| saveCachedBindings(script, new TemporaryBindings(inferencer, |
| visitor.bindings)); |
| ((ThreadTypeSystemImpl) ITypeSystem.CURRENT).set(inferencer); |
| } |
| } |
| |
| public void afterBuild(IBuildContext context) { |
| if (hasDependents) { |
| ((ThreadTypeSystemImpl) ITypeSystem.CURRENT).set(null); |
| } |
| } |
| |
| public void endBuild(IProgressMonitor monitor) { |
| removeCachedBindings(); |
| inferencer = null; |
| } |
| |
| /** |
| * The name of the {@link IBuildContext} attribute containing "bindings" |
| * <code>(Map<ASTNode,IValueReference>)</code> |
| */ |
| public static final String ATTR_BINDINGS = TypeInfoValidator.class |
| .getName() + ".BINDINGS"; |
| |
| private static final int CACHED_BINDINGS_SIZE = 32; |
| |
| /** |
| * Thread specific bindings, so methods from {@link JSBindings} called from |
| * {@link IBuildParticipant} will return validation specific bindings. |
| */ |
| private static final ThreadLocal<Map<Script, JSBindings>> CACHED_BINDINGS = new ThreadLocal<Map<Script, JSBindings>>(); |
| |
| @SuppressWarnings("serial") |
| private static void saveCachedBindings(Script script, JSBindings bindings) { |
| Map<Script, JSBindings> map = CACHED_BINDINGS.get(); |
| if (map == null) { |
| map = new LinkedHashMap<Script, JSBindings>( |
| (CACHED_BINDINGS_SIZE + CACHED_BINDINGS_SIZE / 3), 0.75f, |
| true) { |
| @Override |
| protected boolean removeEldestEntry( |
| Map.Entry<Script, JSBindings> eldest) { |
| return size() >= CACHED_BINDINGS_SIZE; |
| } |
| }; |
| CACHED_BINDINGS.set(map); |
| } |
| map.put(script, bindings); |
| } |
| |
| private static void removeCachedBindings() { |
| final Map<Script, JSBindings> map = CACHED_BINDINGS.get(); |
| if (map != null && !map.isEmpty()) { |
| map.clear(); |
| } |
| } |
| |
| /** |
| * Returns cached "bindings" for the specified script, if any. |
| * |
| * @param script |
| * @return |
| */ |
| public static JSBindings getCachedBindings(Script script) { |
| final Map<Script, JSBindings> map = CACHED_BINDINGS.get(); |
| if (map != null) { |
| return map.get(script); |
| } |
| return null; |
| } |
| |
| protected TypeInferencer2 createTypeInferencer() { |
| return new TypeInferencer2(); |
| } |
| |
| private static enum VisitorMode { |
| NORMAL, CALL |
| } |
| |
| private static abstract class ExpressionValidator { |
| abstract void call(ValidationVisitor visitor); |
| |
| public ExpressionValidator() { |
| } |
| |
| private ISuppressWarningsState suppressed; |
| |
| public ISuppressWarningsState getSuppressed() { |
| return suppressed; |
| } |
| |
| public void setSuppressed(ISuppressWarningsState suppressed) { |
| this.suppressed = suppressed; |
| } |
| |
| public boolean isRelatedTo(IValueReference reference) { |
| return false; |
| } |
| } |
| |
| private static class CallExpressionValidator extends ExpressionValidator { |
| private final FunctionScope scope; |
| private final CallExpression node; |
| private final IValueReference reference; |
| private final IValueReference[] arguments; |
| private final List<IRMethod> methods; |
| |
| public CallExpressionValidator(FunctionScope scope, |
| CallExpression node, IValueReference reference, |
| IValueReference[] arguments, List<IRMethod> methods) { |
| this.scope = scope; |
| this.node = node; |
| this.reference = reference; |
| this.arguments = arguments; |
| this.methods = methods; |
| } |
| |
| @Override |
| public void call(ValidationVisitor visitor) { |
| visitor.validateCallExpression(scope, node, reference, arguments, |
| methods); |
| } |
| |
| @Override |
| public boolean isRelatedTo(IValueReference reference) { |
| return reference.isParentOf(this.reference); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + " - " + reference + "()"; |
| } |
| } |
| |
| private static class ReturnNode { |
| |
| final ReturnStatement node; |
| final IValueReference returnValueReference; |
| |
| public ReturnNode(ReturnStatement node, |
| IValueReference returnValueReference) { |
| this.node = node; |
| this.returnValueReference = returnValueReference; |
| } |
| |
| @Override |
| public String toString() { |
| return String.valueOf(node).trim() + " -> " + returnValueReference; |
| } |
| } |
| |
| private static class TestReturnStatement extends ExpressionValidator { |
| |
| private final List<ReturnNode> lst; |
| private final IRMethod jsMethod; |
| |
| public TestReturnStatement(IRMethod jsMethod, List<ReturnNode> lst) { |
| this.jsMethod = jsMethod; |
| this.lst = lst; |
| } |
| |
| @Override |
| public void call(ValidationVisitor visitor) { |
| final IRType methodType = jsMethod.getType(); |
| IRType firstType = null; |
| ReturnNode firstNode = null; |
| for (ReturnNode element : lst) { |
| if (element.returnValueReference == null) |
| continue; |
| final IRType type = JavaScriptValidations |
| .typeOf(element.returnValueReference); |
| TypeCompatibility compatibility = null; |
| if (methodType instanceof IRTypeExtension) { |
| final IValidationStatus status = ((IRTypeExtension) methodType) |
| .isAssignableFrom(element.returnValueReference); |
| if (status != null) { |
| if (status instanceof TypeCompatibility) { |
| compatibility = (TypeCompatibility) status; |
| } else if (status != ValidationStatus.OK) { |
| JavaScriptValidations |
| .reportValidationStatus( |
| visitor.getProblemReporter(), |
| status, |
| element.node, |
| JavaScriptProblems.DECLARATION_MISMATCH_ACTUAL_RETURN_TYPE, |
| ValidationMessages.DeclarationMismatchWithActualReturnType, |
| jsMethod.getName()); |
| } |
| } |
| } else if (type != null && methodType != null) { |
| compatibility = methodType.isAssignableFrom(type); |
| } |
| if (compatibility != null |
| && compatibility != TypeCompatibility.TRUE) { |
| final ReturnStatement node = element.node; |
| visitor.getProblemReporter() |
| .reportProblem( |
| compatibility == TypeCompatibility.FALSE ? JavaScriptProblems.DECLARATION_MISMATCH_ACTUAL_RETURN_TYPE |
| : JavaScriptProblems.DECLARATION_MISMATCH_ACTUAL_RETURN_TYPE_PARAMETERIZATION, |
| NLS.bind( |
| ValidationMessages.DeclarationMismatchWithActualReturnType, |
| new String[] { |
| jsMethod.getName(), |
| TypeUtil.getName(methodType), |
| TypeUtil.getName(type) }), |
| node.sourceStart(), node.sourceEnd()); |
| } |
| if (methodType == null && firstType == null && type != null) { |
| // remember first type only if return type is not declared. |
| // consistency check makes sense only if no return type |
| // declaration. |
| firstType = type.normalize(); |
| firstNode = element; |
| } |
| } |
| |
| if (firstType != null) { |
| for (ReturnNode next : lst) { |
| if (next == firstNode) { |
| continue; |
| } |
| IRType nextType = JavaScriptValidations |
| .typeOf(next.returnValueReference); |
| if (nextType != null) { |
| nextType = nextType.normalize(); |
| if (!nextType.isAssignableFrom(firstType).ok() |
| && !firstType.isAssignableFrom(nextType).ok()) { |
| visitor.getProblemReporter() |
| .reportProblem( |
| JavaScriptProblems.RETURN_INCONSISTENT, |
| NLS.bind( |
| ValidationMessages.ReturnTypeInconsistentWithPreviousReturn, |
| new String[] { |
| TypeUtil.getName(nextType), |
| TypeUtil.getName(firstType) }), |
| next.node.sourceStart(), |
| next.node.sourceEnd()); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + " - " + jsMethod.getName(); |
| } |
| } |
| |
| private static class NewExpressionValidator extends ExpressionValidator { |
| private final FunctionScope scope; |
| private final NewExpression node; |
| private final IValueReference reference; |
| private final IValueReference typeReference; |
| private final IValueReference[] arguments; |
| private final IValueCollection collection; |
| |
| public NewExpressionValidator(FunctionScope scope, NewExpression node, |
| IValueReference reference, IValueReference typeReference, |
| IValueReference[] arguments, IValueCollection collection) { |
| this.scope = scope; |
| this.node = node; |
| this.reference = reference; |
| this.typeReference = typeReference; |
| this.arguments = arguments; |
| this.collection = collection; |
| } |
| |
| @Override |
| public void call(ValidationVisitor visitor) { |
| visitor.validateNewExpression(scope, collection, |
| node.getObjectClass(), reference, typeReference, arguments); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + " - " + reference; |
| } |
| } |
| |
| private static class PropertyExpressionHolder extends ExpressionValidator { |
| private final FunctionScope scope; |
| private final PropertyExpression node; |
| private final IValueReference reference; |
| private final boolean exists; |
| |
| public PropertyExpressionHolder(FunctionScope scope, |
| PropertyExpression node, IValueReference reference, |
| boolean exists) { |
| this.scope = scope; |
| this.node = node; |
| this.reference = reference; |
| this.exists = exists; |
| } |
| |
| @Override |
| public void call(ValidationVisitor visitor) { |
| visitor.validateProperty(scope, node, reference, exists); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + " - " + reference; |
| } |
| } |
| |
| static class FunctionScope { |
| // Set<Expression or IValueReference> |
| final Set<Object> reported = new HashSet<Object>(); |
| final List<ReturnNode> returnNodes = new ArrayList<ReturnNode>(); |
| boolean throwsException; |
| |
| void add(Path path) { |
| if (path != null) { |
| reported.add(path.start); |
| reported.add(path.references[0]); |
| } |
| } |
| |
| boolean contains(Path path) { |
| if (path != null) { |
| if (reported.contains(path.start)) { |
| return true; |
| } |
| for (IValueReference reference : path.references) { |
| if (reported.contains(reference)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| } |
| |
| static class Path { |
| final Expression start; |
| final IValueReference[] references; |
| |
| public Path(Expression start, IValueReference[] references) { |
| this.start = start; |
| this.references = references; |
| } |
| } |
| |
| public static class ValidationVisitor extends TypeInferencerVisitor { |
| |
| private final List<ExpressionValidator> expressionValidators = new ArrayList<ExpressionValidator>(); |
| private final Set<FunctionStatement> inconsistentReturns; |
| |
| public ValidationVisitor(ITypeInferenceContext context, |
| JSProblemReporter reporter, |
| Set<FunctionStatement> inconsistentReturns) { |
| this(context, reporter, inconsistentReturns, false); |
| } |
| |
| public ValidationVisitor(ITypeInferenceContext context, |
| JSProblemReporter reporter, |
| Set<FunctionStatement> inconsistentReturns, |
| boolean hasDependents) { |
| super(context); |
| this.reporter = reporter; |
| this.inconsistentReturns = inconsistentReturns; |
| this.bindings = hasDependents ? new HashMap<ASTNode, IValueReference>() |
| : null; |
| } |
| |
| private final Map<ASTNode, VisitorMode> modes = new IdentityHashMap<ASTNode, VisitorMode>(); |
| |
| private final Stack<ASTNode> visitStack = new Stack<ASTNode>(); |
| |
| final Map<ASTNode, IValueReference> bindings; |
| |
| @Override |
| public IValueReference visit(ASTNode node) { |
| visitStack.push(node); |
| try { |
| final IValueReference value = super.visit(node); |
| if (bindings != null && value != null) { |
| bindings.put(node, value); |
| } |
| return value; |
| } finally { |
| visitStack.pop(); |
| } |
| } |
| |
| @Override |
| public void initialize() { |
| super.initialize(); |
| modes.clear(); |
| visitStack.clear(); |
| expressionValidators.clear(); |
| variables.clear(); |
| functionScopes.clear(); |
| functionScopes.add(new FunctionScope()); |
| final List<IValidatorExtension> extensions = TypeInfoManager |
| .createExtensions(context, IValidatorExtension.class, null); |
| if (!extensions.isEmpty()) { |
| this.extensions = extensions |
| .toArray(new IValidatorExtension[extensions.size()]); |
| } else { |
| this.extensions = null; |
| } |
| if (getTypeChecker() instanceof ITypeCheckerExtension) { |
| ((ITypeCheckerExtension) getTypeChecker()) |
| .setExtensions(this.extensions); |
| } |
| } |
| |
| private IValidatorExtension[] extensions; |
| |
| /** |
| * Returns {@link ITypeChecker} which can be used for type validations. |
| */ |
| @NonNull |
| @Override |
| public ITypeChecker getTypeChecker() { |
| ITypeChecker result = super.getTypeChecker(); |
| if (result == null) { |
| result = new TypeChecker(context, reporter); |
| typeChecker = result; |
| } |
| return result; |
| } |
| |
| @Override |
| public void done() { |
| super.done(); |
| if (inconsistentReturns != null && !inconsistentReturns.isEmpty() |
| && reporter instanceof Reporter) { |
| final Reporter r = (Reporter) reporter; |
| for (FunctionStatement statement : inconsistentReturns) { |
| FlowValidation.reportInconsistentReturn(r, statement); |
| } |
| } |
| runDelayedValidations(); |
| for (IValueReference variable : variables) { |
| if (variable.getAttribute(IReferenceAttributes.ACCESS) == null) { |
| final IRVariable jsVariable = (IRVariable) variable |
| .getAttribute(IReferenceAttributes.R_VARIABLE); |
| if (jsVariable != null |
| && jsVariable |
| .isSuppressed(JavaScriptProblems.UNUSED_VARIABLE)) |
| continue; |
| final ReferenceLocation location = variable.getLocation(); |
| reporter.reportProblem( |
| JavaScriptProblems.UNUSED_VARIABLE, |
| NLS.bind("Variable {0} is never used", |
| variable.getName()), |
| location.getNameStart(), location.getNameEnd()); |
| } |
| } |
| ((TypeChecker) typeChecker).validate(); |
| } |
| |
| /** |
| * Executes all the delayed validations collected so far. |
| */ |
| public void runDelayedValidations() { |
| if (expressionValidators.isEmpty()) { |
| return; |
| } |
| final ExpressionValidator[] copy = expressionValidators |
| .toArray(new ExpressionValidator[expressionValidators |
| .size()]); |
| expressionValidators.clear(); |
| runExpressionValidations(Arrays.asList(copy)); |
| } |
| |
| /** |
| * Executes the delayed validations for the specified references. |
| */ |
| public void runDelayedValidationsFor(IValueReference... references) { |
| if (expressionValidators.isEmpty() || references.length == 0) { |
| return; |
| } |
| final List<ExpressionValidator> selected = new ArrayList<ExpressionValidator>(); |
| for (Iterator<ExpressionValidator> i = expressionValidators |
| .iterator(); i.hasNext();) { |
| final ExpressionValidator validator = i.next(); |
| for (IValueReference reference : references) { |
| if (validator.isRelatedTo(reference)) { |
| selected.add(validator); |
| i.remove(); |
| break; |
| } |
| } |
| } |
| if (!selected.isEmpty()) { |
| runExpressionValidations(selected); |
| } |
| } |
| |
| private void runExpressionValidations( |
| Iterable<ExpressionValidator> validators) { |
| final ISuppressWarningsState suppressWarnings = reporter |
| .getSuppressWarnings(); |
| try { |
| for (ExpressionValidator validator : validators) { |
| reporter.restoreSuppressWarnings(validator.getSuppressed()); |
| validator.call(this); |
| } |
| } finally { |
| reporter.restoreSuppressWarnings(suppressWarnings); |
| } |
| } |
| |
| private VisitorMode currentMode() { |
| final VisitorMode mode = modes.get(visitStack.peek()); |
| return mode != null ? mode : VisitorMode.NORMAL; |
| } |
| |
| @Override |
| public IValueReference visitNewExpression(NewExpression node) { |
| final VisitNewResult result = visitNew(node); |
| if (result.getTypeValue() != null) { |
| pushExpressionValidator(new NewExpressionValidator( |
| peekFunctionScope(), node, result.getValue(), |
| result.getTypeValue(), result.getArguments(), |
| peekContext())); |
| } |
| return result.getValue(); |
| } |
| |
| private final Stack<FunctionScope> functionScopes = new Stack<FunctionScope>(); |
| |
| private static Path path(Expression expression, |
| IValueReference reference) { |
| final List<IValueReference> refs = new ArrayList<IValueReference>(8); |
| for (;;) { |
| if (expression instanceof PropertyExpression) { |
| expression = ((PropertyExpression) expression).getObject(); |
| } else if (expression instanceof CallExpression) { |
| expression = ((CallExpression) expression).getExpression(); |
| } else if (expression instanceof GetArrayItemExpression) { |
| expression = ((GetArrayItemExpression) expression) |
| .getArray(); |
| } else { |
| break; |
| } |
| refs.add(reference); |
| reference = reference.getParent(); |
| if (reference == null) { |
| return null; |
| } |
| } |
| refs.add(reference); |
| return new Path(expression, refs.toArray(new IValueReference[refs |
| .size()])); |
| } |
| |
| protected final FunctionScope peekFunctionScope() { |
| return functionScopes.peek(); |
| } |
| |
| public void enterFunctionScope() { |
| functionScopes.push(new FunctionScope()); |
| } |
| |
| public void leaveFunctionScope(IRMethod method, |
| FunctionStatement function) { |
| final FunctionScope scope = functionScopes.pop(); |
| if (method != null) { |
| if (inconsistentReturns != null && method.getType() != null |
| && RTypes.isUndefined(method.getType())) { |
| inconsistentReturns.remove(function); |
| } |
| if (!scope.returnNodes.isEmpty()) { |
| // method.setType(context.resolveTypeRef(method.getType())); |
| pushExpressionValidator(new TestReturnStatement(method, |
| scope.returnNodes)); |
| } else if (!scope.throwsException && method.getType() != null |
| && !RTypes.isUndefined(method.getType())) { |
| final ReferenceLocation location = locationOf(method); |
| if (location == null) { |
| return; |
| } |
| reporter.reportProblem( |
| JavaScriptProblems.DECLARATION_MISMATCH_ACTUAL_RETURN_TYPE, |
| NLS.bind( |
| ValidationMessages.DeclarationMismatchNoReturnType, |
| new String[] { method.getName(), |
| TypeUtil.getName(method.getType()) }), |
| location.getNameStart(), location.getNameEnd()); |
| } |
| } |
| } |
| |
| @Override |
| public IValueReference visitFunctionStatement(FunctionStatement node) { |
| enterFunctionScope(); |
| IValueReference reference = super.visitFunctionStatement(node); |
| final IRMethod method = (IRMethod) reference.getAttribute(R_METHOD); |
| leaveFunctionScope(method, node); |
| |
| return reference; |
| } |
| |
| @Override |
| protected JSMethod createMethod(FunctionStatement node) { |
| validateHidesByFunction(node); |
| return super.createMethod(node); |
| } |
| |
| private void validateHidesByFunction(FunctionStatement node) { |
| IValueCollection peekContext = peekContext(); |
| final boolean inlineBlock = node.isInlineBlock(); |
| for (Argument argument : node.getArguments()) { |
| if (inlineBlock |
| && ITypeNames.UNDEFINED.equals(argument |
| .getArgumentName())) { |
| // TODO (alex) alternatively, set Element.isHideAllowed() |
| // for the "undefined" property |
| continue; |
| } |
| IValueReference child = peekContext.getChild(argument |
| .getArgumentName()); |
| if (child.exists()) { |
| if (child.getKind() == ReferenceKind.PROPERTY) { |
| final Property property = ValueReferenceUtil |
| .extractElement(child, Property.class); |
| if (!property.isHideAllowed()) { |
| if (property.getDeclaringType() != null) { |
| reporter.reportProblem( |
| JavaScriptProblems.PARAMETER_HIDES_VARIABLE, |
| NLS.bind( |
| ValidationMessages.ParameterHidesPropertyOfType, |
| new String[] { |
| argument.getArgumentName(), |
| property.getDeclaringType() |
| .getName() }), |
| argument.sourceStart(), argument |
| .sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.PARAMETER_HIDES_VARIABLE, |
| NLS.bind( |
| ValidationMessages.ParameterHidesProperty, |
| argument.getArgumentName()), |
| argument.sourceStart(), argument |
| .sourceEnd()); |
| } |
| } |
| } else if (!Boolean.TRUE.equals(child |
| .getAttribute(IReferenceAttributes.HIDE_ALLOWED))) { |
| if (child.getKind() == ReferenceKind.FUNCTION) { |
| reporter.reportProblem( |
| JavaScriptProblems.PARAMETER_HIDES_FUNCTION, |
| NLS.bind( |
| ValidationMessages.ParameterHidesFunction, |
| argument.getArgumentName()), |
| argument.sourceStart(), argument |
| .sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.PARAMETER_HIDES_VARIABLE, |
| NLS.bind( |
| ValidationMessages.ParameterHidesVariable, |
| argument.getArgumentName()), |
| argument.sourceStart(), argument |
| .sourceEnd()); |
| } |
| } |
| } |
| } |
| if (node.isDeclaration()) { |
| final IValueReference child; |
| final IValueCollection parentScope = getParentScope(peekContext); |
| if (parentScope == null) { |
| child = peekContext.getChild(node.getName().getName()); |
| if (getSource().equals(child.getLocation().getSource())) { |
| return; |
| } |
| } else { |
| child = parentScope.getChild(node.getName().getName()); |
| } |
| if (child.exists()) { |
| final ReferenceKind kind = child.getKind(); |
| if (kind == ReferenceKind.PROPERTY) { |
| final Property property = ValueReferenceUtil |
| .extractElement(child, Property.class); |
| if (!property.isHideAllowed()) { |
| if (property.getDeclaringType() != null) { |
| reporter.reportProblem( |
| JavaScriptProblems.FUNCTION_HIDES_VARIABLE, |
| NLS.bind( |
| ValidationMessages.FunctionHidesPropertyOfType, |
| new String[] { |
| node.getName() |
| .getName(), |
| property.getDeclaringType() |
| .getName() }), |
| node.getName().sourceStart(), node |
| .getName().sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.FUNCTION_HIDES_VARIABLE, |
| NLS.bind( |
| ValidationMessages.FunctionHidesProperty, |
| node.getName().getName()), node |
| .getName().sourceStart(), node |
| .getName().sourceEnd()); |
| } |
| } |
| } else if (!Boolean.TRUE.equals(child |
| .getAttribute(IReferenceAttributes.HIDE_ALLOWED))) { |
| if (kind == ReferenceKind.FUNCTION) { |
| reporter.reportProblem( |
| JavaScriptProblems.FUNCTION_HIDES_FUNCTION, |
| NLS.bind( |
| ValidationMessages.FunctionHidesFunction, |
| node.getName().getName()), node |
| .getName().sourceStart(), node |
| .getName().sourceEnd()); |
| } else if (kind == ReferenceKind.LOCAL |
| || kind == ReferenceKind.GLOBAL) { |
| reporter.reportProblem( |
| JavaScriptProblems.FUNCTION_HIDES_VARIABLE, |
| NLS.bind( |
| ValidationMessages.FunctionHidesVariable, |
| node.getName().getName()), node |
| .getName().sourceStart(), node |
| .getName().sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.FUNCTION_HIDES_PREDEFINED, |
| NLS.bind( |
| ValidationMessages.FunctionHidesPredefinedIdentifier, |
| node.getName().getName()), node |
| .getName().sourceStart(), node |
| .getName().sourceEnd()); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public IValueReference visitReturnStatement(ReturnStatement node) { |
| IValueReference returnValueReference = super |
| .visitReturnStatement(node); |
| if (node.getValue() != null) { |
| peekFunctionScope().returnNodes.add(new ReturnNode(node, |
| returnValueReference)); |
| } |
| return returnValueReference; |
| } |
| |
| @Override |
| public IValueReference visitThrowStatement(ThrowStatement node) { |
| peekFunctionScope().throwsException = true; |
| return super.visitThrowStatement(node); |
| } |
| |
| @Override |
| public IValueReference visitCallExpression(CallExpression node) { |
| final Expression expression = node.getExpression(); |
| modes.put(expression, VisitorMode.CALL); |
| |
| final IValueReference reference = visit(expression); |
| modes.remove(expression); |
| if (reference == null) { |
| visitList(node.getArguments()); |
| return null; |
| } |
| if (reference.getAttribute(PHANTOM, true) != null) { |
| visitList(node.getArguments()); |
| return PhantomValueReference.REFERENCE; |
| } |
| if (isUntyped(reference)) { |
| visitList(node.getArguments()); |
| return null; |
| } |
| if (reference.getKind() == ReferenceKind.ARGUMENT) { |
| if (reference.getDeclaredTypes().contains(RTypes.FUNCTION)) { |
| for (ASTNode argument : node.getArguments()) { |
| visit(argument); |
| } |
| // don't validate function pointer |
| return null; |
| } |
| } |
| final List<ASTNode> args = node.getArguments(); |
| final IValueReference[] arguments = new IValueReference[args.size()]; |
| for (int i = 0, size = args.size(); i < size; ++i) { |
| arguments[i] = visit(args.get(i)); |
| } |
| final List<IRMethod> methods = ValueReferenceUtil.extractElements( |
| reference, IRMethod.class); |
| if (methods != null && methods.size() == 1) { |
| final IRMethod method = methods.get(0); |
| IValueReference ref = checkSpecialJavascriptFunctionCalls( |
| reference, arguments, method); |
| if (ref != null) |
| return ref; |
| if (method.isGeneric()) { |
| if (!JavaScriptValidations.checkParameterCount(method, |
| args.size())) { |
| final Expression methodNode = expression instanceof PropertyExpression ? ((PropertyExpression) expression) |
| .getProperty() : expression; |
| reportMethodParameterError(methodNode, arguments, |
| method); |
| return null; |
| } |
| final IRType result = evaluateGenericCall(method, arguments); |
| return ConstantValue.of(result); |
| } else { |
| pushExpressionValidator(new CallExpressionValidator( |
| peekFunctionScope(), node, reference, arguments, |
| methods)); |
| return ConstantValue.of(method.getType()); |
| } |
| } else { |
| pushExpressionValidator(new CallExpressionValidator( |
| peekFunctionScope(), node, reference, arguments, |
| methods)); |
| if (methods != null && methods.size() > 1) { |
| // try to found the best match |
| IRMethod bestMatch = null; |
| outer: for (IRMethod method : methods) { |
| if (method.getParameterCount() == arguments.length) { |
| for (int i = 0; i < arguments.length; i++) { |
| TypeCompatibility tc = testArgumentType(method |
| .getParameters().get(i).getType(), |
| arguments[i]); |
| if (tc != TypeCompatibility.TRUE) { |
| continue outer; |
| } |
| } |
| // both match, which one is the more specific one |
| if (bestMatch != null) { |
| List<IRParameter> parameters = method |
| .getParameters(); |
| for (int i = 0; i < parameters.size(); i++) { |
| IRType type = parameters.get(i).getType(); |
| if (type != null) { |
| IRType bestMatchType = bestMatch |
| .getParameters().get(i) |
| .getType(); |
| if (bestMatchType == null) { |
| break; |
| } |
| if (type.isAssignableFrom(bestMatchType) == TypeCompatibility.TRUE) { |
| continue outer; |
| } |
| } |
| } |
| |
| } |
| bestMatch = method; |
| } |
| } |
| if (bestMatch != null) |
| return ConstantValue.of(bestMatch.getType()); |
| } |
| final IRType expressionType = JavaScriptValidations |
| .typeOf(reference); |
| if (expressionType != null) { |
| if (expressionType instanceof IRFunctionType) { |
| return ConstantValue |
| .of(((IRFunctionType) expressionType) |
| .getReturnType()); |
| } else if (expressionType instanceof IRClassType) { |
| final IRTypeDeclaration target = ((IRClassType) expressionType) |
| .getDeclaration(); |
| if (target != null) { |
| final IRConstructor constructor = target |
| .getStaticConstructor(); |
| if (constructor != null |
| && constructor.getType() != null) { |
| return new ConstantValue(constructor.getType()); |
| } |
| } |
| } |
| } |
| } |
| return reference.getChild(IValueReference.FUNCTION_OP); |
| } |
| |
| private void pushExpressionValidator( |
| ExpressionValidator expressionValidator) { |
| expressionValidator.setSuppressed(reporter.getSuppressWarnings()); |
| expressionValidators.add(expressionValidator); |
| } |
| |
| /** |
| * @param node |
| * @param reference |
| * @param methods |
| * @return |
| */ |
| protected void validateCallExpression(FunctionScope scope, |
| CallExpression node, final IValueReference reference, |
| IValueReference[] arguments, List<IRMethod> methods) { |
| |
| final Expression expression = node.getExpression(); |
| final Path path = path(expression, reference); |
| if (scope.contains(path)) { |
| return; |
| } |
| final Expression methodNode; |
| if (expression instanceof PropertyExpression) { |
| methodNode = ((PropertyExpression) expression).getProperty(); |
| } else { |
| methodNode = expression; |
| } |
| |
| if (methods == null || methods.size() == 0) |
| methods = ValueReferenceUtil.extractElements(reference, |
| IRMethod.class); |
| if (methods != null) { |
| IRMethod method = JavaScriptValidations.selectMethod(methods, |
| arguments, true); |
| if (method == null) { |
| final IRType type = JavaScriptValidations.typeOf(reference |
| .getParent()); |
| if (type != null) { |
| if (TypeUtil.kind(type) == TypeKind.JAVA) { |
| reporter.reportProblem( |
| JavaScriptProblems.WRONG_JAVA_PARAMETERS, |
| NLS.bind( |
| ValidationMessages.MethodNotSelected, |
| new String[] { |
| reference.getName(), |
| type.getName(), |
| describeArguments(arguments) }), |
| methodNode.sourceStart(), methodNode |
| .sourceEnd()); |
| |
| } else { |
| // TODO also a JS error (that should be |
| // configurable) |
| } |
| } |
| } else { |
| validateCallExpressionMethod(node, reference, arguments, |
| methodNode, method); |
| } |
| return; |
| } |
| final Object attrRMethod = reference.getAttribute(R_METHOD, true); |
| if (attrRMethod instanceof IRMethod) { |
| validateCallExpressionRMethod(reference, arguments, methodNode, |
| (IRMethod) attrRMethod); |
| return; |
| } |
| final IRType expressionType = JavaScriptValidations |
| .typeOf(reference); |
| if (expressionType != null) { |
| if (expressionType instanceof IRFunctionType) { |
| validateCallExpressionRMethod(reference, arguments, |
| methodNode, new RMethodFunctionWrapper( |
| (IRFunctionType) expressionType, reference)); |
| return; |
| } else if (expressionType instanceof IRClassType) { |
| final IRTypeDeclaration target = ((IRClassType) expressionType) |
| .getDeclaration(); |
| if (target != null) { |
| final IRConstructor constructor = target |
| .getStaticConstructor(); |
| if (constructor != null) { |
| validateCallExpressionMethod(node, reference, |
| arguments, methodNode, constructor); |
| return; |
| } |
| } |
| } else if (expressionType != RTypes.any() |
| && expressionType != RTypes.none() |
| && !RTypes.FUNCTION.isAssignableFrom(expressionType) |
| .ok()) { |
| if (expressionType instanceof IRUnionType) { |
| if (expressionType.isAssignableFrom(RTypes.FUNCTION) |
| .ok()) |
| return; |
| } |
| reporter.reportProblem( |
| JavaScriptProblems.WRONG_FUNCTION, |
| isIdentifier(expression) ? NLS.bind( |
| ValidationMessages.WrongFunction, |
| reference.getName()) |
| : ValidationMessages.WrongFunctionExpression, |
| methodNode.sourceStart(), methodNode.sourceEnd()); |
| return; |
| } |
| // we've got expressionType, reference exists, so return. |
| return; |
| } |
| scope.add(path); |
| if (!isDynamicArrayAccess(reference) |
| && !isUntypedParameter(reference)) { |
| |
| final IRType type = JavaScriptValidations.typeOf(reference |
| .getParent()); |
| if (type != null) { |
| if (type == RTypes.any()) { |
| return; |
| } |
| if (TypeUtil.kind(type) == TypeKind.JAVA) { |
| reporter.reportProblem( |
| JavaScriptProblems.UNDEFINED_JAVA_METHOD, |
| NLS.bind(ValidationMessages.UndefinedMethod, |
| reference.getName(), type.getName()), |
| methodNode.sourceStart(), methodNode |
| .sourceEnd()); |
| } else if (!reference.exists()) { |
| reporter.reportProblem( |
| JavaScriptProblems.UNDEFINED_METHOD, |
| NLS.bind( |
| ValidationMessages.UndefinedMethodOnObject, |
| reference.getName(), reference |
| .getParent().getName()), |
| methodNode.sourceStart(), methodNode |
| .sourceEnd()); |
| } |
| } else { |
| if (expression instanceof NewExpression) { |
| if (reference.getKind() == ReferenceKind.TYPE) { |
| return; |
| } |
| IRType newType = JavaScriptValidations |
| .typeOf(reference); |
| if (newType != null) { |
| return; |
| } |
| } |
| if (expression instanceof NewExpression) { |
| reporter.reportProblem( |
| JavaScriptProblems.WRONG_TYPE_EXPRESSION, |
| NLS.bind( |
| ValidationMessages.UndefinedJavascriptType, |
| ((NewExpression) expression) |
| .getObjectClass() |
| .toSourceString("")), |
| methodNode.sourceStart(), methodNode |
| .sourceEnd()); |
| |
| } else { |
| if (reference.getParent() == null) { |
| if (isIdentifier(expression) && !reference.exists()) { |
| reporter.reportProblem( |
| JavaScriptProblems.UNDEFINED_FUNCTION, |
| NLS.bind( |
| ValidationMessages.UndefinedMethodInScript, |
| reference.getName()), |
| methodNode.sourceStart(), methodNode |
| .sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.WRONG_FUNCTION, |
| isIdentifier(expression) ? NLS |
| .bind(ValidationMessages.WrongFunction, |
| reference.getName()) |
| : ValidationMessages.WrongFunctionExpression, |
| methodNode.sourceStart(), methodNode |
| .sourceEnd()); |
| } |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.UNDEFINED_METHOD, |
| NLS.bind( |
| ValidationMessages.UndefinedMethodOnObject, |
| reference.getName(), reference |
| .getParent().getName()), |
| methodNode.sourceStart(), methodNode |
| .sourceEnd()); |
| } |
| } |
| } |
| } |
| } |
| |
| private void validateCallExpressionRMethod( |
| final IValueReference reference, IValueReference[] arguments, |
| final Expression methodNode, IRMethod method) { |
| if (method.isDeprecated()) { |
| String name = reference.getName(); |
| if (name == null || "".equals(name)) { |
| name = method.getName(); |
| } |
| reporter.reportProblem(JavaScriptProblems.DEPRECATED_FUNCTION, |
| NLS.bind(ValidationMessages.DeprecatedFunction, |
| name), methodNode.sourceStart(), |
| methodNode.sourceEnd()); |
| } |
| validateAccessibility(methodNode, reference, method); |
| final List<IRParameter> parameters = method.getParameters(); |
| final TypeCompatibility compatibility = validateParameters( |
| parameters, arguments, methodNode); |
| if (compatibility != TypeCompatibility.TRUE) { |
| String name = method.getName(); |
| if (name == null) { |
| Identifier identifier = PropertyExpressionUtils |
| .getIdentifier(methodNode); |
| if (identifier != null) |
| name = identifier.getName(); |
| } |
| final IProblemIdentifier problemId; |
| if (method.isTyped()) { |
| if (compatibility == TypeCompatibility.FALSE) { |
| problemId = JavaScriptProblems.WRONG_PARAMETERS; |
| } else { |
| problemId = JavaScriptProblems.WRONG_PARAMETERS_PARAMETERIZATION; |
| } |
| } else { |
| problemId = JavaScriptProblems.WRONG_PARAMETERS_UNTYPED; |
| } |
| reporter.reportProblem(problemId, NLS.bind( |
| ValidationMessages.MethodNotApplicableInScript, |
| new String[] { name, describeParamTypes(parameters), |
| describeArguments(arguments, parameters) }), |
| methodNode.sourceStart(), methodNode.sourceEnd()); |
| } |
| } |
| |
| private void validateCallExpressionMethod(CallExpression node, |
| final IValueReference reference, IValueReference[] arguments, |
| final Expression methodNode, IRMethod method) { |
| if (method.getVisibility() != Visibility.PUBLIC) { |
| if (!validateAccessibility(methodNode, method)) { |
| return; |
| } |
| } |
| if (method.isDeprecated()) { |
| reportDeprecatedMethod(methodNode, reference, method); |
| } |
| if (!JavaScriptValidations.checkParameterCount(method, node |
| .getArguments().size())) { |
| reportMethodParameterError(methodNode, arguments, method); |
| return; |
| } |
| final List<IRParameter> parameters = method.getParameters(); |
| final TypeCompatibility compatibility = validateParameters( |
| parameters, arguments, methodNode); |
| if (compatibility != TypeCompatibility.TRUE) { |
| String name = method.getName(); |
| if (name == null) { |
| Identifier identifier = PropertyExpressionUtils |
| .getIdentifier(methodNode); |
| if (identifier != null) |
| name = identifier.getName(); |
| } |
| reporter.reportProblem( |
| compatibility == TypeCompatibility.FALSE ? JavaScriptProblems.WRONG_PARAMETERS |
| : JavaScriptProblems.WRONG_PARAMETERS_PARAMETERIZATION, |
| NLS.bind( |
| ValidationMessages.MethodNotApplicableInScript, |
| new String[] { |
| name, |
| describeParamTypes(parameters), |
| describeArguments(arguments, parameters) }), |
| methodNode.sourceStart(), methodNode.sourceEnd()); |
| } |
| } |
| |
| /** |
| * Checks if the passed reference is an untyped parameter. This method |
| * helps to identify the common case of callbacks. |
| * |
| * @param reference |
| * @return |
| */ |
| private boolean isUntypedParameter(IValueReference reference) { |
| return reference.getKind() == ReferenceKind.ARGUMENT |
| && reference.getDeclaredType() == null; |
| } |
| |
| public static boolean isUntyped(IValueReference reference) { |
| while (reference != null) { |
| final ReferenceKind kind = reference.getKind(); |
| if (kind == ReferenceKind.ARGUMENT) { |
| final IRType type = reference.getDeclaredType(); |
| if (type == null || type == RTypes.any()) { |
| return true; |
| } |
| } else if (kind == ReferenceKind.THIS |
| && reference.getDeclaredType() == null |
| && reference.getDirectChildren().isEmpty()) { |
| return true; |
| } |
| reference = reference.getParent(); |
| } |
| return false; |
| } |
| |
| /** |
| * Tests if reference has array access somewhere above which is not |
| * applied to {@link IRArrayType} or {@link IRMapType} types. |
| */ |
| private boolean isDynamicArrayAccess(IValueReference reference) { |
| for (; reference != null; reference = reference.getParent()) { |
| if (reference.getName() == IValueReference.ARRAY_OP) { |
| final IRType containerType = JavaScriptValidations |
| .typeOf(reference.getParent()); |
| if (containerType instanceof IRArrayType |
| || containerType instanceof IRMapType) { |
| break; |
| } |
| // ignore array lookup function calls |
| // like: array[1](), |
| // those are dynamic. |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void reportDeprecatedMethod(ASTNode methodNode, |
| IValueReference reference, IRMethod method) { |
| if (method.getDeclaringType() != null) { |
| reporter.reportProblem( |
| JavaScriptProblems.DEPRECATED_METHOD, |
| NLS.bind(ValidationMessages.DeprecatedMethod, reference |
| .getName(), method.getDeclaringType().getName()), |
| methodNode.sourceStart(), methodNode.sourceEnd()); |
| } else { |
| reporter.reportProblem(JavaScriptProblems.DEPRECATED_METHOD, |
| NLS.bind(ValidationMessages.DeprecatedTopLevelMethod, |
| reference.getName()), methodNode.sourceStart(), |
| methodNode.sourceEnd()); |
| } |
| } |
| |
| private void reportDeprecatedRecordMember(ASTNode node, |
| IValueReference reference, IRRecordMember method) { |
| IRType type = JavaScriptValidations.typeOf(reference.getParent()); |
| final String msg = NLS.bind(ValidationMessages.DeprecatedProperty, |
| method.getName(), type != null ? type.getName() : null); |
| reporter.reportProblem(JavaScriptProblems.DEPRECATED_PROPERTY, msg, |
| node.sourceStart(), node.sourceEnd()); |
| } |
| |
| private void reportMethodParameterError(ASTNode methodNode, |
| IValueReference[] arguments, IRMethod method) { |
| if (method.getDeclaringType() != null) { |
| IProblemIdentifier problemId = JavaScriptProblems.WRONG_PARAMETERS; |
| if (method.getDeclaringType().getKind() == TypeKind.JAVA) { |
| problemId = JavaScriptProblems.WRONG_JAVA_PARAMETERS; |
| } |
| reporter.reportProblem(problemId, NLS.bind( |
| ValidationMessages.MethodNotApplicable, |
| new String[] { method.getName(), |
| describeParamTypes(method.getParameters()), |
| method.getDeclaringType().getName(), |
| describeArguments(arguments) }), methodNode |
| .sourceStart(), methodNode.sourceEnd()); |
| } else { |
| reporter.reportProblem(JavaScriptProblems.WRONG_PARAMETERS, NLS |
| .bind(ValidationMessages.TopLevelMethodNotApplicable, |
| new String[] { |
| method.getName(), |
| describeParamTypes(method |
| .getParameters()), |
| describeArguments(arguments) }), |
| methodNode.sourceStart(), methodNode.sourceEnd()); |
| } |
| } |
| |
| private final List<ValidationStatus> statuses = new ArrayList<ValidationStatus>(); |
| |
| private TypeCompatibility validateParameters( |
| List<IRParameter> parameters, IValueReference[] arguments, |
| ISourceNode problemNode) { |
| if (arguments.length > parameters.size() |
| && !(parameters.size() > 0 && parameters.get( |
| parameters.size() - 1).getKind() == ParameterKind.VARARGS)) |
| return TypeCompatibility.FALSE; |
| int testTypesSize = parameters.size(); |
| if (parameters.size() > arguments.length) { |
| for (int i = arguments.length; i < parameters.size(); i++) { |
| final IRParameter p = parameters.get(i); |
| if (!p.isOptional() && !p.isVarargs()) |
| return TypeCompatibility.FALSE; |
| } |
| testTypesSize = arguments.length; |
| } else if (parameters.size() < arguments.length) { |
| // is var args.. |
| testTypesSize = parameters.size() - 1; |
| } |
| |
| statuses.clear(); |
| TypeCompatibility result = TypeCompatibility.TRUE; |
| for (int i = 0; i < testTypesSize; i++) { |
| final IValueReference argument = arguments[i]; |
| final IRParameter parameter = parameters.get(i); |
| if (parameter.getType() instanceof IRTypeExtension) { |
| final IValidationStatus status = ((IRTypeExtension) parameter |
| .getType()).isAssignableFrom(argument); |
| if (status instanceof TypeCompatibility) { |
| final TypeCompatibility pResult = (TypeCompatibility) status; |
| if (pResult.after(result)) { |
| if (pResult == TypeCompatibility.FALSE |
| && statuses.isEmpty()) { |
| return pResult; |
| } |
| result = pResult; |
| } |
| } else if (status instanceof ValidationStatus) { |
| statuses.add((ValidationStatus) status); |
| } else if (status instanceof ValidationMultiStatus) { |
| Collections.addAll(statuses, |
| ((ValidationMultiStatus) status).getChildren()); |
| } |
| } else { |
| final TypeCompatibility pResult = testArgumentType( |
| parameter.getType(), argument); |
| if (pResult.after(result)) { |
| if (pResult == TypeCompatibility.FALSE |
| && statuses.isEmpty()) { |
| return pResult; |
| } |
| result = pResult; |
| } |
| } |
| } |
| // test var args |
| if (parameters.size() < arguments.length) { |
| int varargsParameter = parameters.size() - 1; |
| IRType paramType = parameters.get(varargsParameter).getType(); |
| |
| for (int i = varargsParameter; i < arguments.length; i++) { |
| IValueReference argument = arguments[i]; |
| final TypeCompatibility pResult = testArgumentType( |
| paramType, argument); |
| if (pResult.after(result)) { |
| if (pResult == TypeCompatibility.FALSE |
| && statuses.isEmpty()) { |
| return pResult; |
| } |
| result = pResult; |
| } |
| } |
| |
| } |
| if (!statuses.isEmpty()) { |
| for (ValidationStatus status : statuses) { |
| final int start; |
| final int end; |
| if (status.hasRange()) { |
| start = status.start(); |
| end = status.end(); |
| } else { |
| start = problemNode.start(); |
| end = problemNode.end(); |
| } |
| reporter.reportProblem(status.identifier(), |
| status.message(), start, end); |
| } |
| return TypeCompatibility.TRUE; |
| } |
| return result; |
| } |
| |
| /** |
| * @param paramType |
| * @param argument |
| * @return |
| */ |
| private TypeCompatibility testArgumentType(IRType paramType, |
| IValueReference argument) { |
| if (argument != null && paramType != null) { |
| final IRType argumentType; |
| if (argument.getAttribute(IReferenceAttributes.R_METHOD) != null) { |
| IRMethod method = (IRMethod) argument |
| .getAttribute(IReferenceAttributes.R_METHOD); |
| argumentType = RTypes.functionType(getContext(), |
| method.getParameters(), method.getType()); |
| } else { |
| argumentType = JavaScriptValidations |
| .typeOf(argument); |
| } |
| if (argumentType != null) { |
| return paramType.isAssignableFrom(argumentType); |
| } |
| } |
| return TypeCompatibility.TRUE; |
| } |
| |
| private String describeParamTypes(List<IRParameter> parameters) { |
| StringBuilder sb = new StringBuilder(); |
| for (IRParameter parameter : parameters) { |
| if (sb.length() != 0) { |
| sb.append(','); |
| } |
| if (parameter.getType() instanceof IRRecordType) { |
| sb.append('{'); |
| for (IRRecordMember member : ((IRRecordType) parameter |
| .getType()).getMembers()) { |
| if (sb.length() > 1) |
| sb.append(", "); |
| final boolean optional = member.isOptional(); |
| if (optional) |
| sb.append('['); |
| sb.append(member.getName()); |
| if (member.getType() != null) { |
| sb.append(':'); |
| sb.append(member.getType().getName()); |
| } |
| if (optional) |
| sb.append(']'); |
| } |
| sb.append('}'); |
| } else if (parameter.getType() != null) { |
| if (parameter.getKind() == ParameterKind.OPTIONAL) |
| sb.append("["); |
| if (parameter.getKind() == ParameterKind.VARARGS) |
| sb.append("..."); |
| sb.append(parameter.getType().getName()); |
| if (parameter.getKind() == ParameterKind.OPTIONAL) |
| sb.append("]"); |
| } else { |
| sb.append('?'); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Describes the specified arguments. |
| */ |
| private String describeArguments(IValueReference[] arguments) { |
| return describeArguments(arguments, |
| Collections.<IRParameter> emptyList()); |
| } |
| |
| /** |
| * Describes the specified arguments, expanding properties for those |
| * where record type parameter is expected. |
| */ |
| private String describeArguments(IValueReference[] arguments, |
| List<IRParameter> parameters) { |
| final StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < arguments.length; i++) { |
| final IValueReference argument = arguments[i]; |
| final IRParameter parameter = i < parameters.size() ? parameters |
| .get(i) : null; |
| if (sb.length() != 0) { |
| sb.append(','); |
| } |
| if (argument == null) { |
| sb.append("null"); |
| } else if (parameter != null |
| && parameter.getType() instanceof IRRecordType) { |
| describeRecordTypeArgument(sb, argument, |
| (IRRecordType) parameter.getType()); |
| } else if (argument.getAttribute(IReferenceAttributes.R_METHOD) != null) { |
| IRMethod method = (IRMethod) argument |
| .getAttribute(IReferenceAttributes.R_METHOD); |
| sb.append(RTypes.functionType(getContext(), |
| method.getParameters(), method.getType()).getName()); |
| } else if (argument.getDeclaredType() != null) { |
| sb.append(argument.getDeclaredType().getName()); |
| } else { |
| final JSTypeSet types = argument.getTypes(); |
| if (types.size() > 0) { |
| sb.append(types.toRType().getName()); |
| } else { |
| sb.append('?'); |
| } |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Describes the specified argument which is expected to be of the |
| * record type. |
| */ |
| private void describeRecordTypeArgument(StringBuilder sb, |
| IValueReference argument, @Nullable IRRecordType expectedType) { |
| sb.append('{'); |
| boolean appendComma = false; |
| HashSet<String> children = new HashSet<String>( |
| argument.getDirectChildren()); |
| JSTypeSet types = argument.getTypes(); |
| for (IRType type : types) { |
| if (type instanceof IRRecordType) { |
| Collection<IRRecordMember> members = ((IRRecordType) type) |
| .getMembers(); |
| for (IRRecordMember member : members) { |
| children.add(member.getName()); |
| } |
| } |
| } |
| for (String childName : children) { |
| if (appendComma) |
| sb.append(", "); |
| appendComma = true; |
| sb.append(childName); |
| |
| final IRType expectedMemberType; |
| if (expectedType != null) { |
| final IRRecordMember member = expectedType |
| .getMember(childName); |
| expectedMemberType = member != null ? member.getType() |
| : null; |
| } else { |
| expectedMemberType = null; |
| } |
| if (expectedMemberType instanceof IRRecordType) { |
| sb.append(": "); |
| describeRecordTypeArgument(sb, |
| argument.getChild(childName), |
| (IRRecordType) expectedMemberType); |
| } else { |
| final IValueReference child = argument.getChild(childName); |
| final IRType type = JavaScriptValidations.typeOf(child); |
| if (type != null) { |
| if (expectedType != null |
| && type.getName().equals(ITypeNames.OBJECT) |
| && !child.getDirectChildren().isEmpty()) { |
| sb.append(": "); |
| describeRecordTypeArgument(sb, child, null); |
| } else { |
| sb.append(':'); |
| sb.append(type.getName()); |
| } |
| } |
| } |
| } |
| sb.append('}'); |
| } |
| |
| @Override |
| public IValueReference visitPropertyExpression(PropertyExpression node) { |
| final IValueReference result = super.visitPropertyExpression(node); |
| if (result == null || result.getAttribute(PHANTOM, true) != null |
| || isUntyped(result)) { |
| return result; |
| } |
| if (currentMode() != VisitorMode.CALL) { |
| pushExpressionValidator(new PropertyExpressionHolder( |
| peekFunctionScope(), node, result, result.exists())); |
| } |
| return result; |
| } |
| |
| @Override |
| protected IValueReference visitAssign(IValueReference left, |
| IValueReference right, BinaryOperation node) { |
| if (left != null) { |
| checkAssign(left, node); |
| } |
| return super.visitAssign(left, right, node); |
| } |
| |
| private static boolean isVarOrFunction(IValueReference reference) { |
| final ReferenceKind kind = reference.getKind(); |
| return kind.isVariable() || kind == ReferenceKind.FUNCTION; |
| } |
| |
| private static boolean isAccess(Identifier node) { |
| return isAccess(node, node.getParent()); |
| } |
| |
| private static boolean isAccess(Identifier node, final ASTNode parent) { |
| if (parent instanceof BinaryOperation) { |
| return !((BinaryOperation) parent).isAssignmentTo(node); |
| } else if (parent instanceof StatementBlock |
| || parent instanceof Script) { |
| return false; |
| } else if (parent instanceof UnaryOperation) { |
| final UnaryOperation operation = (UnaryOperation) parent; |
| final int op = operation.getOperation(); |
| return op != JSParser.INC && op != JSParser.DEC |
| && op != JSParser.PINC && op != JSParser.PDEC |
| || isAccess(node, operation.getParent()); |
| } else { |
| return true; |
| } |
| } |
| |
| @Override |
| public IValueReference visitIdentifier(Identifier node) { |
| final IValueReference result = super.visitIdentifier(node); |
| if (isAccess(node) && isVarOrFunction(result) |
| && getSource().equals(result.getLocation().getSource())) { |
| if (result.getAttribute(IReferenceAttributes.ACCESS) == null) { |
| result.setAttribute(IReferenceAttributes.ACCESS, |
| Boolean.TRUE); |
| } |
| } |
| final IRProperty property = ValueReferenceUtil.extractElement( |
| result, IRProperty.class); |
| if (property != null && property.isDeprecated()) { |
| reportDeprecatedProperty(property, null, node); |
| } else { |
| if (!result.exists() && !isParentCallOrNew(node)) { |
| peekFunctionScope().add(path(node, result)); |
| reporter.reportProblem( |
| JavaScriptProblems.UNDECLARED_VARIABLE, NLS.bind( |
| ValidationMessages.UndeclaredVariable, |
| node.getName()), node.sourceStart(), node |
| .sourceEnd()); |
| } else { |
| validateAccessibility(node, result, null); |
| if (result.exists() |
| && node.getParent() instanceof BinaryOperation |
| && ((BinaryOperation) node.getParent()) |
| .getOperation() == JSParser.INSTANCEOF |
| && ((BinaryOperation) node.getParent()) |
| .getRightExpression() == node) { |
| checkTypeReference(node, |
| JavaScriptValidations.typeOf(result), |
| peekContext()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private boolean isParentCallOrNew(Identifier node) { |
| final JSNode parent = node.getParent(); |
| if (parent instanceof CallExpression) { |
| return ((CallExpression) parent).getExpression() == node; |
| } else if (parent instanceof NewExpression) { |
| return ((NewExpression) parent).getObjectClass() == node; |
| } else { |
| return false; |
| } |
| } |
| |
| private static IValueCollection getParentScope( |
| final IValueCollection collection) { |
| IValueCollection c = collection; |
| while (c != null && !c.isScope()) { |
| c = c.getParent(); |
| } |
| if (c != null) { |
| c = c.getParent(); |
| if (c != null) { |
| return c; |
| } |
| } |
| return null; |
| } |
| |
| private final List<IValueReference> variables = new ArrayList<IValueReference>(); |
| |
| @Override |
| protected IValueReference createVariable(IValueCollection context, |
| VariableDeclaration declaration) { |
| validateHidesByVariable(context, declaration); |
| final IValueReference variable = super.createVariable(context, |
| declaration); |
| if (context.getParent() != null |
| || canValidateUnusedVariable(context, variable)) { |
| variables.add(variable); |
| } |
| return variable; |
| } |
| |
| private boolean canValidateUnusedVariable(IValueCollection collection, |
| IValueReference reference) { |
| if (extensions != null) { |
| for (IValidatorExtension extension : extensions) { |
| final IValidatorExtension.UnusedVariableValidation result = extension |
| .canValidateUnusedVariable(collection, reference); |
| if (result != null) { |
| return result == IValidatorExtension.UnusedVariableValidation.TRUE; |
| } |
| } |
| } |
| return isPrivate(reference); |
| } |
| |
| private static boolean isPrivate(IValueReference reference) { |
| final IVariable variable = (IVariable) reference |
| .getAttribute(IReferenceAttributes.VARIABLE); |
| return variable != null |
| && variable.getVisibility() == Visibility.PRIVATE; |
| } |
| |
| private void checkAssign(IValueReference reference, ASTNode node) { |
| final Object value = reference |
| .getAttribute(IAssignProtection.ATTRIBUTE); |
| if (value != null) { |
| final IAssignProtection assign; |
| if (value instanceof IAssignProtection2) { |
| if (((IAssignProtection2) value).isReadOnly(reference)) { |
| assign = (IAssignProtection) value; |
| } else { |
| return; |
| } |
| } else { |
| assign = value instanceof IAssignProtection ? (IAssignProtection) value |
| : PROTECT_CONST; |
| } |
| reporter.reportProblem(assign.problemId(), |
| assign.problemMessage(), node.sourceStart(), |
| node.sourceEnd()); |
| } else if (reference.getKind() == ReferenceKind.FUNCTION) { |
| // test if it is not a function override of a super local type class. |
| Set<String> directChildren = null; |
| if (reference.getParent() != null) |
| if (reference.getParent().getName() |
| .equals(IRLocalType.PROTOTYPE_PROPERTY)) { |
| directChildren = Collections.emptySet(); // just ignore |
| // it if it |
| // is an |
| // assignment |
| // to |
| // prototype |
| } else { |
| directChildren = reference.getParent() |
| .getDirectChildren( |
| IValue.NO_LOCAL_TYPES); |
| } |
| if (directChildren == null |
| || directChildren.contains(reference.getName())) { |
| reporter.reportProblem(JavaScriptProblems.UNASSIGNABLE_ELEMENT, |
| ValidationMessages.UnassignableFunction, |
| node.sourceStart(), node.sourceEnd()); |
| } |
| } |
| } |
| |
| @Override |
| protected void initializeVariable(IValueReference reference, |
| VariableDeclaration declaration) { |
| if (declaration.getInitializer() != null |
| && declaration.getParent() instanceof VariableStatement) { |
| checkAssign(reference, declaration); |
| } |
| super.initializeVariable(reference, declaration); |
| } |
| |
| private void validateHidesByVariable(IValueCollection context, |
| VariableDeclaration declaration) { |
| final IValueReference child; |
| final Identifier identifier = declaration.getIdentifier(); |
| final IValueCollection parentScope = getParentScope(context); |
| if (parentScope == null) { |
| child = context.getChild(identifier.getName()); |
| if (getSource().equals(child.getLocation().getSource())) { |
| return; |
| } |
| } else { |
| child = parentScope.getChild(identifier.getName()); |
| } |
| if (child.exists()) { |
| final ReferenceKind kind = child.getKind(); |
| if (kind == ReferenceKind.ARGUMENT) { |
| reporter.reportProblem( |
| JavaScriptProblems.VAR_HIDES_PARAMETER, NLS.bind( |
| ValidationMessages.VariableHidesParameter, |
| declaration.getVariableName()), identifier |
| .sourceStart(), identifier.sourceEnd()); |
| } else if (kind == ReferenceKind.FUNCTION) { |
| reporter.reportProblem( |
| JavaScriptProblems.VAR_HIDES_FUNCTION, NLS.bind( |
| ValidationMessages.VariableHidesFunction, |
| declaration.getVariableName()), identifier |
| .sourceStart(), identifier.sourceEnd()); |
| } else if (kind == ReferenceKind.PROPERTY) { |
| final Property property = ValueReferenceUtil |
| .extractElement(child, Property.class); |
| if (property != null && property.getDeclaringType() != null) { |
| reporter.reportProblem( |
| JavaScriptProblems.VAR_HIDES_PROPERTY, |
| NLS.bind( |
| ValidationMessages.VariableHidesPropertyOfType, |
| declaration.getVariableName(), property |
| .getDeclaringType().getName()), |
| identifier.sourceStart(), identifier |
| .sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.VAR_HIDES_PROPERTY, |
| NLS.bind( |
| ValidationMessages.VariableHidesProperty, |
| declaration.getVariableName()), |
| identifier.sourceStart(), identifier |
| .sourceEnd()); |
| |
| } |
| } else if (kind == ReferenceKind.METHOD) { |
| final IRMethod method = ValueReferenceUtil.extractElement( |
| child, IRMethod.class); |
| if (method != null && method.getDeclaringType() != null) { |
| reporter.reportProblem( |
| JavaScriptProblems.VAR_HIDES_METHOD, |
| NLS.bind( |
| ValidationMessages.VariableHidesMethodOfType, |
| declaration.getVariableName(), method |
| .getDeclaringType().getName()), |
| identifier.sourceStart(), identifier |
| .sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.VAR_HIDES_METHOD, NLS.bind( |
| ValidationMessages.VariableHidesMethod, |
| declaration.getVariableName()), |
| identifier.sourceStart(), identifier |
| .sourceEnd()); |
| } |
| } else if (kind == ReferenceKind.LOCAL |
| || kind == ReferenceKind.GLOBAL) { |
| reporter.reportProblem( |
| JavaScriptProblems.DUPLICATE_VAR_DECLARATION, |
| NLS.bind(ValidationMessages.VariableHidesVariable, |
| declaration.getVariableName()), identifier |
| .sourceStart(), identifier.sourceEnd()); |
| } else { |
| reporter.reportProblem( |
| JavaScriptProblems.VAR_HIDES_PREDEFINED, |
| NLS.bind( |
| ValidationMessages.VariableHidesPredefinedIdentifier, |
| declaration.getVariableName()), identifier |
| .sourceStart(), identifier.sourceEnd()); |
| } |
| } |
| } |
| |
| protected void validateProperty(final FunctionScope scope, |
| PropertyExpression propertyExpression, IValueReference result, |
| boolean exists) { |
| final Path path = path(propertyExpression, result); |
| if (scope.contains(path)) { |
| return; |
| } |
| final Expression propName = propertyExpression.getProperty(); |
| final IRMember member = ValueReferenceUtil.extractElement(result, |
| IRMember.class); |
| if (member != null) { |
| if (member.isDeprecated()) { |
| final IRProperty parentProperty = ValueReferenceUtil |
| .extractElement(result.getParent(), |
| IRProperty.class); |
| if (parentProperty != null |
| && parentProperty.getDeclaringType() == null) { |
| if (member instanceof IRProperty) |
| reportDeprecatedProperty((IRProperty) member, |
| parentProperty, propName); |
| else if (member instanceof IRMethod) |
| reportDeprecatedMethod(propName, result, |
| (IRMethod) member); |
| } else { |
| if (member instanceof IRProperty) |
| reportDeprecatedProperty((IRProperty) member, |
| member.getDeclaringType(), propName); |
| else if (member instanceof IRMethod) |
| reportDeprecatedMethod(propName, result, |
| (IRMethod) member); |
| else if (member instanceof IRRecordMember) |
| reportDeprecatedRecordMember(propName, result, |
| (IRRecordMember) member); |
| } |
| } else if (!member.isVisible()) { |
| final IRProperty parentProperty = ValueReferenceUtil |
| .extractElement(result.getParent(), |
| IRProperty.class); |
| if (parentProperty != null |
| && parentProperty.getDeclaringType() == null) { |
| if (member instanceof IRProperty) |
| reportHiddenProperty((IRProperty) member, |
| parentProperty, propName); |
| // else if (member instanceof Method) |
| // reportDeprecatedMethod(propName, result, |
| // (Method) member); |
| } else if (member instanceof IRProperty) { |
| reportHiddenProperty((IRProperty) member, |
| member.getDeclaringType(), propName); |
| } |
| // } else if |
| // (JavaScriptValidations.isStatic(result.getParent()) |
| // && !member.isStatic()) { |
| // IRType type = JavaScriptValidations.typeOf(result |
| // .getParent()); |
| // reporter.reportProblem( |
| // JavaScriptProblems.INSTANCE_PROPERTY, |
| // NLS.bind( |
| // ValidationMessages.StaticReferenceToNoneStaticProperty, |
| // result.getName(), TypeUtil.getName(type)), |
| // propName.sourceStart(), propName.sourceEnd()); |
| // } else if |
| // (!JavaScriptValidations.isStatic(result.getParent()) |
| // && member.isStatic()) { |
| // IRType type = JavaScriptValidations.typeOf(result |
| // .getParent()); |
| // reporter.reportProblem( |
| // JavaScriptProblems.STATIC_PROPERTY, |
| // NLS.bind( |
| // ValidationMessages.ReferenceToStaticProperty, |
| // result.getName(), type.getName()), propName |
| // .sourceStart(), propName.sourceEnd()); |
| } else if (member.getVisibility() != Visibility.PUBLIC) { |
| validateAccessibility(propName, result, member); |
| } |
| } else if ((!exists && !result.exists()) |
| && !isDynamicArrayAccess(result)) { |
| scope.add(path); |
| final IRType parentType = typeOf(result.getParent()); |
| if (parentType != null && parentType.isExtensible()) { |
| return; |
| } |
| final TypeKind parentKind = TypeUtil.kind(parentType); |
| if (parentType != null && parentKind == TypeKind.JAVA) { |
| reporter.reportProblem( |
| JavaScriptProblems.UNDEFINED_JAVA_PROPERTY, NLS |
| .bind(ValidationMessages.UndefinedProperty, |
| result.getName(), |
| parentType.getName()), propName |
| .sourceStart(), propName.sourceEnd()); |
| } else if (!belongsToLogicalExpression(propertyExpression)) { |
| if (parentType != null |
| && (parentKind == TypeKind.JAVASCRIPT || parentKind == TypeKind.PREDEFINED)) { |
| reporter.reportProblem( |
| JavaScriptProblems.UNDEFINED_PROPERTY, |
| NLS.bind( |
| ValidationMessages.UndefinedPropertyInScriptType, |
| result.getName(), parentType.getName()), |
| propName.sourceStart(), propName.sourceEnd()); |
| } else { |
| final String parentPath = PropertyExpressionUtils |
| .getPath(propertyExpression.getObject()); |
| reporter.reportProblem( |
| JavaScriptProblems.UNDEFINED_PROPERTY, |
| NLS.bind( |
| ValidationMessages.UndefinedPropertyInScript, |
| result.getName(), |
| parentPath != null ? parentPath |
| : "javascript"), propName |
| .sourceStart(), propName.sourceEnd()); |
| } |
| } |
| } else { |
| IRVariable variable = (IRVariable) result |
| .getAttribute(IReferenceAttributes.R_VARIABLE); |
| if (variable != null) { |
| if (variable.isDeprecated()) { |
| reporter.reportProblem( |
| JavaScriptProblems.DEPRECATED_VARIABLE, |
| NLS.bind(ValidationMessages.DeprecatedVariable, |
| variable.getName()), propName |
| .sourceStart(), propName.sourceEnd()); |
| } |
| validateAccessibility(propName, result, variable); |
| return; |
| } else { |
| IRMethod method = (IRMethod) result.getAttribute(R_METHOD); |
| if (method != null) { |
| if (method.isDeprecated()) { |
| boolean report = true; |
| JSNode parent = propertyExpression.getParent(); |
| if (parent instanceof BinaryOperation) { |
| Expression rightExpression = ((BinaryOperation) parent) |
| .getRightExpression(); |
| report = !(rightExpression instanceof FunctionStatement); |
| } |
| if (report) |
| reporter.reportProblem( |
| JavaScriptProblems.DEPRECATED_FUNCTION, |
| NLS.bind( |
| ValidationMessages.DeprecatedFunction, |
| method.getName()), propName |
| .sourceStart(), propName |
| .sourceEnd()); |
| } |
| validateAccessibility(propName, result, method); |
| return; |
| |
| } |
| } |
| } |
| } |
| |
| /** |
| * Tests if the specified expression is part of {@link BinaryOperation} |
| * combining parts with logical AND or OR. |
| */ |
| private boolean belongsToLogicalExpression(Expression expression) { |
| if (expression.getParent() instanceof BinaryOperation) { |
| final BinaryOperation bo = (BinaryOperation) expression |
| .getParent(); |
| return bo.getOperation() == JSParser.LAND |
| || bo.getOperation() == JSParser.LOR; |
| } |
| return false; |
| } |
| |
| private void reportDeprecatedProperty(IRProperty property, |
| IRElement owner, ASTNode node) { |
| final String msg; |
| if (owner instanceof IRType) { |
| msg = NLS.bind(ValidationMessages.DeprecatedProperty, |
| property.getName(), owner.getName()); |
| } else if (owner instanceof IRProperty) { |
| msg = NLS.bind(ValidationMessages.DeprecatedPropertyOfInstance, |
| property.getName(), owner.getName()); |
| } else { |
| msg = NLS.bind(ValidationMessages.DeprecatedPropertyNoType, |
| property.getName()); |
| } |
| reporter.reportProblem(JavaScriptProblems.DEPRECATED_PROPERTY, msg, |
| node.sourceStart(), node.sourceEnd()); |
| } |
| |
| private void reportHiddenProperty(IRProperty property, IRElement owner, |
| ASTNode node) { |
| final String msg; |
| if (owner instanceof IRType) { |
| msg = NLS.bind(ValidationMessages.HiddenProperty, |
| property.getName(), owner.getName()); |
| } else if (owner instanceof IRProperty) { |
| msg = NLS.bind(ValidationMessages.HiddenPropertyOfInstance, |
| property.getName(), owner.getName()); |
| } else { |
| msg = NLS.bind(ValidationMessages.HiddenPropertyNoType, |
| property.getName()); |
| } |
| reporter.reportProblem(JavaScriptProblems.HIDDEN_PROPERTY, msg, |
| node.sourceStart(), node.sourceEnd()); |
| } |
| |
| private static boolean isIdentifier(Expression node) { |
| return node instanceof Identifier || node instanceof CallExpression |
| && isIdentifier(((CallExpression) node).getExpression()); |
| } |
| |
| private static Identifier getIdentifier(Expression node) { |
| if (node instanceof Identifier) { |
| return (Identifier) node; |
| } else if (node instanceof CallExpression) { |
| return getIdentifier(((CallExpression) node).getExpression()); |
| } else { |
| return null; |
| } |
| } |
| |
| private IRTypeDeclaration extractClassType(IValueReference typeReference) { |
| IRType type = typeReference.getDeclaredType(); |
| if (type == null) { |
| final JSTypeSet types = typeReference.getTypes(); |
| if (types.size() > 0) { |
| type = types.toRType(); |
| } |
| } |
| if (type != null && type instanceof IRClassType) { |
| return ((IRClassType) type).getDeclaration(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Lazy validation of the {@link NewExpression}. |
| */ |
| protected void validateNewExpression(FunctionScope scope, |
| IValueCollection collection, Expression node, |
| IValueReference reference, IValueReference typeReference, |
| IValueReference[] arguments) { |
| final Identifier identifier = getIdentifier(node); |
| final Expression problemNode = identifier != null ? identifier |
| : node; |
| if (typeReference.getParent() == null && isIdentifier(node) |
| && !typeReference.exists()) { |
| scope.add(path(node, typeReference)); |
| reportUnknownType(JavaScriptProblems.UNDECLARED_VARIABLE, |
| problemNode, identifier != null ? identifier.getName() |
| : "?"); |
| return; |
| } |
| final IRTypeDeclaration type = extractClassType(typeReference); |
| if (type != null) { |
| if (type.getKind() != TypeKind.UNKNOWN) { |
| if (!validateInstantiability(problemNode, type.getSource(), |
| typeReference)) { |
| return; |
| } |
| checkTypeReference(problemNode, type); |
| final List<IRConstructor> constructors = TypeUtil |
| .findConstructors(type); |
| if (!constructors.isEmpty()) { |
| final IRConstructor constructor = JavaScriptValidations |
| .selectMethod(constructors, arguments, false); |
| if (constructor == null) { |
| reporter.reportProblem( |
| JavaScriptProblems.WRONG_PARAMETERS, |
| NLS.bind( |
| "The constructor {0}({1}) is undefined", |
| new String[] { |
| typeReference.getName(), |
| describeArguments(arguments) }), |
| problemNode.sourceStart(), problemNode |
| .sourceEnd()); |
| return; |
| } |
| if (constructor.isDeprecated()) { |
| reportDeprecatedMethod(problemNode, typeReference, |
| constructor); |
| } |
| final List<IRParameter> parameters = context |
| .contextualize(constructor, type) |
| .getParameters(); |
| final TypeCompatibility compatibility = validateParameters( |
| parameters, arguments, problemNode); |
| if (compatibility != TypeCompatibility.TRUE) { |
| reporter.reportProblem( |
| compatibility == TypeCompatibility.FALSE ? JavaScriptProblems.WRONG_PARAMETERS |
| : JavaScriptProblems.WRONG_PARAMETERS_PARAMETERIZATION, |
| NLS.bind( |
| "The constructor {0}({1}) is not applicable for the arguments ({2})", |
| new String[] { |
| typeReference.getName(), |
| describeParamTypes(parameters), |
| describeArguments( |
| arguments, |
| parameters) }), |
| problemNode.sourceStart(), problemNode |
| .sourceEnd()); |
| } |
| } |
| } |
| } else { |
| if (typeReference.getKind() == ReferenceKind.FUNCTION) { |
| final Object attrRMethod = typeReference.getAttribute( |
| R_METHOD, true); |
| if (attrRMethod instanceof IRMethod) { |
| validateCallExpressionRMethod(reference, arguments, |
| problemNode, (IRMethod) attrRMethod); |
| return; |
| } |
| } else { |
| final String lazyName = ValueReferenceUtil |
| .getLazyName(reference); |
| if (lazyName != null) { |
| reportProblem( |
| JavaScriptProblems.WRONG_TYPE_EXPRESSION, |
| bind(ValidationMessages.UndefinedJavascriptType, |
| lazyName), node); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param node |
| * @param type |
| */ |
| protected void checkTypeReference(ASTNode node, IRType type, |
| IValueCollection collection) { |
| if (type == null) { |
| return; |
| } |
| if (type instanceof IRClassType) { |
| final IRTypeDeclaration t = ((IRClassType) type) |
| .getDeclaration(); |
| if (t != null && t.getKind() != TypeKind.UNKNOWN) { |
| checkTypeReference(node, t); |
| } |
| } |
| } |
| |
| private void checkTypeReference(ASTNode node, final IRTypeDeclaration t) { |
| if (t.isDeprecated()) { |
| reporter.reportProblem(JavaScriptProblems.DEPRECATED_TYPE, NLS |
| .bind(ValidationMessages.DeprecatedType, t.getName()), |
| node.sourceStart(), node.sourceEnd()); |
| } |
| } |
| |
| /** |
| * Validates instantiability of the specified type. Returns |
| * <code>true</code> if type could be instantiated and |
| * <code>false</code> otherwise. |
| * |
| * @param node |
| * @param type |
| * @param typeReference |
| * @return |
| */ |
| private boolean validateInstantiability(ASTNode node, final Type type, |
| IValueReference typeReference) { |
| if (extensions != null) { |
| for (IValidatorExtension extension : extensions) { |
| final IValidationStatus result = extension.canInstantiate( |
| type, typeReference); |
| if (result != null) { |
| if (result == ValidationStatus.OK) { |
| return true; |
| } else { |
| JavaScriptValidations.reportValidationStatus( |
| reporter, result, node, |
| JavaScriptProblems.NON_INSTANTIABLE_TYPE, |
| ValidationMessages.NonInstantiableType, |
| type.getName()); |
| return false; |
| } |
| } |
| } |
| } |
| if (!type.isInstantiable()) { |
| reporter.reportProblem( |
| JavaScriptProblems.NON_INSTANTIABLE_TYPE, |
| NLS.bind(ValidationMessages.NonInstantiableType, |
| type.getName()), node.sourceStart(), |
| node.sourceEnd()); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Tests if the member is accessible. Returns <code>true</code> if |
| * access is allowed and <code>false</code> otherwise. |
| * |
| * @param node |
| * @param member |
| * @return |
| */ |
| private boolean validateAccessibility(ASTNode node, IRMember member) { |
| if (extensions != null && member.getSource() instanceof Member) { |
| final Member source = (Member) member.getSource(); |
| for (IValidatorExtension extension : extensions) { |
| final IValidationStatus result = extension |
| .validateAccessibility(source); |
| if (result != null) { |
| if (result == ValidationStatus.OK) { |
| return true; |
| } else { |
| JavaScriptValidations.reportValidationStatus( |
| reporter, result, node, |
| JavaScriptProblems.INACCESSIBLE_MEMBER, |
| ValidationMessages.InaccessibleMember, |
| member.getName()); |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| private MemberValidationEvent memberValidationEvent; |
| |
| /** |
| * Tests if the specified member is accessible. |
| * |
| * @param expression |
| * AST node |
| * @param reference |
| * evaluated value reference |
| * @param member |
| * runtime variable/function reference if already evaluated |
| * or <code>null</code> if not evaluated yet |
| */ |
| private void validateAccessibility(Expression expression, |
| IValueReference reference, IRMember member) { |
| if (extensions != null) { |
| if (memberValidationEvent == null) { |
| memberValidationEvent = new MemberValidationEvent(); |
| } |
| memberValidationEvent.set(reference, member); |
| for (IValidatorExtension extension : extensions) { |
| final IValidationStatus result = extension |
| .validateAccessibility(memberValidationEvent); |
| if (result != null) { |
| if (result == ValidationStatus.OK) { |
| return; |
| } else { |
| IRMember rMember = memberValidationEvent |
| .getRMember(); |
| JavaScriptValidations.reportValidationStatus( |
| reporter, result, expression, |
| JavaScriptProblems.INACCESSIBLE_MEMBER, |
| ValidationMessages.InaccessibleMember, |
| rMember == null ? reference.getName() |
| : rMember.getName()); |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| @Deprecated |
| public void reportUnknownType(IProblemIdentifier identifier, |
| String message, ASTNode node, String name) { |
| reportProblem(identifier, NLS.bind(message, name), node); |
| } |
| |
| public void reportUnknownType(IProblemIdentifier identifier, |
| ASTNode node, String name) { |
| reportProblem(identifier, |
| bind(ValidationMessages.UnknownType, name), node); |
| } |
| |
| public void reportProblem(IProblemIdentifier identifier, |
| String message, ISourceNode node) { |
| reporter.reportProblem(identifier, message, node.start(), |
| node.end()); |
| } |
| |
| private static boolean stronglyTyped(IValueReference reference) { |
| final IRType parentType = typeOf(reference); |
| if (parentType != null) { |
| if (parentType instanceof IRRecordType) { |
| return true; |
| } |
| return TypeUtil.kind(parentType) == TypeKind.JAVA; |
| } |
| return false; |
| } |
| |
| @Override |
| public IValueReference visitIfStatement(IfStatement node) { |
| final IValueReference condition = visit(node.getCondition()); |
| if (condition != null && !condition.exists() |
| && node.getCondition() instanceof PropertyExpression) { |
| final IValueReference parent = condition.getParent(); |
| if (parent != null && parent.exists() && !stronglyTyped(parent)) { |
| if (DEBUG) { |
| System.out.println("visitIfStatement(" |
| + node.getCondition() + ") doesn't exist " |
| + condition + " - create it"); |
| } |
| condition.setValue(PhantomValueReference.REFERENCE); |
| } |
| } |
| visitIfStatements(node); |
| return null; |
| } |
| } |
| |
| static final boolean DEBUG = false; |
| |
| } |