/******************************************************************************* * Copyright (c) 2010 xored software, Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * xored software, Inc. - initial API and Implementation (Alex Panchenko) *******************************************************************************/ package org.eclipse.dltk.internal.javascript.validation; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.dltk.compiler.problem.ProblemSeverity; import org.eclipse.dltk.core.builder.IBuildContext; import org.eclipse.dltk.core.builder.IBuildParticipant; import org.eclipse.dltk.javascript.ast.AbstractNavigationVisitor; import org.eclipse.dltk.javascript.ast.BinaryOperation; import org.eclipse.dltk.javascript.ast.BreakStatement; import org.eclipse.dltk.javascript.ast.ContinueStatement; 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.Keywords; import org.eclipse.dltk.javascript.ast.Label; import org.eclipse.dltk.javascript.ast.LabelledStatement; import org.eclipse.dltk.javascript.ast.LoopStatement; import org.eclipse.dltk.javascript.ast.ObjectInitializer; import org.eclipse.dltk.javascript.ast.ObjectInitializerPart; import org.eclipse.dltk.javascript.ast.PropertyExpression; import org.eclipse.dltk.javascript.ast.PropertyInitializer; import org.eclipse.dltk.javascript.ast.Script; import org.eclipse.dltk.javascript.ast.UnaryOperation; import org.eclipse.dltk.javascript.core.JavaScriptProblems; import org.eclipse.dltk.javascript.parser.JSParser; import org.eclipse.dltk.javascript.parser.Reporter; import org.eclipse.osgi.util.NLS; public class CodeValidation extends AbstractNavigationVisitor<Object> implements IBuildParticipant { private Reporter reporter; private Scope scope; static class LabelInfo { final LabelledStatement statement; boolean finished; public LabelInfo(LabelledStatement statement) { this.statement = statement; } } static class Scope { private final Map<String, LabelInfo> labels = new HashMap<String, LabelInfo>(); public boolean addLabel(LabelledStatement statement) { final String label = statement.getLabel().getText(); if (labels.containsKey(label)) { return false; } labels.put(label, new LabelInfo(statement)); return true; } public LabelInfo getLabel(String label) { return labels.get(label); } public void removeLabel(LabelledStatement statement) { LabelInfo info = labels.get(statement.getLabel().getText()); if (info != null) { info.finished = true; } } } public void build(IBuildContext context) throws CoreException { final Script script = JavaScriptValidations.parse(context); if (script == null) { return; } reporter = JavaScriptValidations.createReporter(context); scope = new Scope(); visit(script); } @Override public Object visitFunctionStatement(FunctionStatement node) { final Scope savedScope = scope; scope = new Scope(); final Object result = super.visitFunctionStatement(node); scope = savedScope; return result; } @Override public Object visitLabelledStatement(LabelledStatement node) { boolean added = scope.addLabel(node); final Object result = super.visitLabelledStatement(node); if (added) { scope.removeLabel(node); } return result; } @Override public Object visitBreakStatement(BreakStatement node) { if (node.getLabel() != null) { validateLabel(node.getLabel(), node.sourceStart(), JSParser.BREAK); } return super.visitBreakStatement(node); } @Override public Object visitContinueStatement(ContinueStatement node) { if (node.getLabel() != null) { validateLabel(node.getLabel(), node.sourceStart(), JSParser.CONTINUE); } return super.visitContinueStatement(node); } private void validateLabel(Label label, int statementStart, int token) { final LabelInfo info = scope.getLabel(label.getText()); if (info == null) { return; } if (info.finished) { reporter.setMessage( token == JSParser.BREAK ? JavaScriptProblems.BREAK_OUTSIDE_LABEL : JavaScriptProblems.CONTINUE_OUTSIDE_LABEL, Keywords.fromToken(token) + " outside of labelled statement"); reporter.setSeverity(ProblemSeverity.ERROR); reporter.setRange(statementStart, label.sourceEnd()); reporter.report(); return; } if (!info.finished && info.statement.getStatement() instanceof LoopStatement) { return; } if (token == JSParser.BREAK) { return; } reporter.setMessage(JavaScriptProblems.CONTINUE_NON_LOOP_LABEL, "continue can only use labels of iteration statements"); reporter.setSeverity(ProblemSeverity.ERROR); reporter.setRange(statementStart, label.sourceEnd()); reporter.report(); } @Override protected void visitCondition(Expression condition) { super.visitCondition(condition); if (condition instanceof BinaryOperation) { BinaryOperation operation = (BinaryOperation) condition; if (operation.getOperation() == JSParser.ASSIGN) { reporter.reportProblem(JavaScriptProblems.EQUAL_AS_ASSIGN, "Test for equality (==) mistyped as assignment (=)?", condition.sourceStart(), condition.sourceEnd()); } } } @Override public Object visitBinaryOperation(BinaryOperation node) { if (node.getOperation() == JSParser.ASSIGN && !canAssignTo(node.getLeftExpression())) { reporter.reportProblem(JavaScriptProblems.INVALID_ASSIGN_LEFT, "Invalid assignment left-hand side.", node.sourceStart(), node.sourceEnd()); } return super.visitBinaryOperation(node); } @Override public Object visitUnaryOperation(UnaryOperation node) { if (isIncDec(node.getOperation()) && !canAssignTo(node.getExpression())) { reporter.reportProblem(JavaScriptProblems.INVALID_ASSIGN_LEFT, "Invalid assignment left-hand side.", node.sourceStart(), node.sourceEnd()); } return super.visitUnaryOperation(node); } private boolean isIncDec(int operation) { return operation == JSParser.INC || operation == JSParser.DEC || operation == JSParser.PINC || operation == JSParser.PDEC; } private boolean canAssignTo(Expression expression) { // TODO what else? XML? return expression instanceof Identifier || expression instanceof PropertyExpression || expression instanceof GetArrayItemExpression; } @Override public Object visitObjectInitializer(ObjectInitializer node) { final Set<String> processed = new HashSet<String>(); for (ObjectInitializerPart part : node.getInitializers()) { if (part instanceof PropertyInitializer) { final PropertyInitializer property = (PropertyInitializer) part; final String propertyName = property.getNameAsString(); if (propertyName != null && !processed.add(propertyName)) { reporter.reportProblem( JavaScriptProblems.DUPLICATE_PROPERTY_IN_LITERAL, NLS.bind( "Duplicate property {0} in object literal", propertyName), property.getName() .sourceStart(), property.getName() .sourceEnd()); } } } return super.visitObjectInitializer(node); } }