/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.error; import com.google.dart.engine.internal.context.RecordingErrorListener; import com.google.dart.engine.source.Source; import com.google.dart.engine.utilities.io.PrintStringWriter; import com.google.dart.engine.utilities.source.LineInfo; import junit.framework.Assert; import junit.framework.AssertionFailedError; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Instances of the class {@code GatheringErrorListener} implement an error listener that collects * all of the errors passed to it for later examination. */ public class GatheringErrorListener implements AnalysisErrorListener { /** * The source being parsed. */ private String rawSource; /** * The source being parsed after inserting a marker at the beginning and end of the range of the * most recent error. */ @SuppressWarnings("unused") private String markedSource; /** * A list containing the errors that were collected. */ private List<AnalysisError> errors = new ArrayList<AnalysisError>(); /** * A table mapping sources to the line information for the source. */ private HashMap<Source, LineInfo> lineInfoMap = new HashMap<Source, LineInfo>(); /** * An empty array of errors used when no errors are expected. */ private static final AnalysisError[] NO_ERRORS = new AnalysisError[0]; /** * Initialize a newly created error listener to collect errors. */ public GatheringErrorListener() { this(null); } /** * Initialize a newly created error listener to collect errors. */ public GatheringErrorListener(String rawSource) { this.rawSource = rawSource; this.markedSource = rawSource; } /** * Add all of the given errors to this listener. * * @param the errors to be added */ public void addAll(AnalysisError[] errors) { for (AnalysisError error : errors) { onError(error); } } /** * Add all of the errors recorded by the given listener to this listener. * * @param listener the listener that has recorded the errors to be added */ public void addAll(RecordingErrorListener listener) { addAll(listener.getErrors()); } /** * Assert that the number of errors that have been gathered matches the number of errors that are * given and that they have the expected error codes and locations. The order in which the errors * were gathered is ignored. * * @param errorCodes the errors that should have been gathered * @throws AssertionFailedError if a different number of errors have been gathered than were * expected or if they do not have the same codes and locations */ public void assertErrors(AnalysisError... expectedErrors) { if (errors.size() != expectedErrors.length) { fail(expectedErrors); } List<AnalysisError> remainingErrors = new ArrayList<AnalysisError>(); for (AnalysisError error : expectedErrors) { remainingErrors.add(error); } for (AnalysisError error : errors) { if (!foundAndRemoved(remainingErrors, error)) { fail(expectedErrors); } } } /** * Assert that the number of errors that have been gathered matches the number of errors that are * given and that they have the expected error codes. The order in which the errors were gathered * is ignored. * * @param expectedErrorCodes the error codes of the errors that should have been gathered * @throws AssertionFailedError if a different number of errors have been gathered than were * expected */ public void assertErrorsWithCodes(ErrorCode... expectedErrorCodes) { StringBuilder builder = new StringBuilder(); // // Verify that the expected error codes have a non-empty message. // for (ErrorCode errorCode : expectedErrorCodes) { Assert.assertFalse("Empty error code message", errorCode.getMessage().isEmpty()); } // // Compute the expected number of each type of error. // HashMap<ErrorCode, Integer> expectedCounts = new HashMap<ErrorCode, Integer>(); for (ErrorCode code : expectedErrorCodes) { Integer count = expectedCounts.get(code); if (count == null) { count = Integer.valueOf(1); } else { count = Integer.valueOf(count.intValue() + 1); } expectedCounts.put(code, count); } // // Compute the actual number of each type of error. // HashMap<ErrorCode, ArrayList<AnalysisError>> errorsByCode = new HashMap<ErrorCode, ArrayList<AnalysisError>>(); for (AnalysisError error : errors) { ErrorCode code = error.getErrorCode(); ArrayList<AnalysisError> list = errorsByCode.get(code); if (list == null) { list = new ArrayList<AnalysisError>(); errorsByCode.put(code, list); } list.add(error); } // // Compare the expected and actual number of each type of error. // for (Map.Entry<ErrorCode, Integer> entry : expectedCounts.entrySet()) { ErrorCode code = entry.getKey(); int expectedCount = entry.getValue().intValue(); int actualCount; ArrayList<AnalysisError> list = errorsByCode.remove(code); if (list == null) { actualCount = 0; } else { actualCount = list.size(); } if (actualCount != expectedCount) { if (builder.length() == 0) { builder.append("Expected "); } else { builder.append("; "); } builder.append(expectedCount); builder.append(" errors of type "); builder.append(code.getClass().getSimpleName() + "." + code); builder.append(", found "); builder.append(actualCount); } } // // Check that there are no more errors in the actual-errors map, otherwise, record message. // for (Map.Entry<ErrorCode, ArrayList<AnalysisError>> entry : errorsByCode.entrySet()) { ErrorCode code = entry.getKey(); ArrayList<AnalysisError> actualErrors = entry.getValue(); int actualCount = actualErrors.size(); if (builder.length() == 0) { builder.append("Expected "); } else { builder.append("; "); } builder.append("0 errors of type "); builder.append(code.getClass().getSimpleName() + "." + code); builder.append(", found "); builder.append(actualCount); builder.append(" ("); for (int i = 0; i < actualErrors.size(); i++) { AnalysisError error = actualErrors.get(i); if (i > 0) { builder.append(", "); } builder.append(error.getOffset()); } builder.append(")"); } if (builder.length() > 0) { Assert.fail(builder.toString()); } } /** * Assert that the number of errors that have been gathered matches the number of severities that * are given and that there are the same number of errors and warnings as specified by the * argument. The order in which the errors were gathered is ignored. * * @param expectedSeverities the severities of the errors that should have been gathered * @throws AssertionFailedError if a different number of errors have been gathered than were * expected */ public void assertErrorsWithSeverities(ErrorSeverity... expectedSeverities) { int expectedErrorCount = 0; int expectedWarningCount = 0; for (ErrorSeverity severity : expectedSeverities) { if (severity == ErrorSeverity.ERROR) { expectedErrorCount++; } else { expectedWarningCount++; } } int actualErrorCount = 0; int actualWarningCount = 0; for (AnalysisError error : errors) { if (error.getErrorCode().getErrorSeverity() == ErrorSeverity.ERROR) { actualErrorCount++; } else { actualWarningCount++; } } if (expectedErrorCount != actualErrorCount || expectedWarningCount != actualWarningCount) { Assert.fail("Expected " + expectedErrorCount + " errors and " + expectedWarningCount + " warnings, found " + actualErrorCount + " errors and " + actualWarningCount + " warnings"); } } /** * Assert that no errors have been gathered. * * @throws AssertionFailedError if any errors have been gathered */ public void assertNoErrors() { assertErrors(NO_ERRORS); } /** * Return the errors that were collected. * * @return the errors that were collected */ public List<AnalysisError> getErrors() { return errors; } /** * Return the line information associated with the given source, or {@code null} if no line * information has been associated with the source. * * @param source the source with which the line information is associated * @return the line information associated with the source */ public LineInfo getLineInfo(Source source) { return lineInfoMap.get(source); } /** * Return {@code true} if an error with the given error code has been gathered. * * @param errorCode the error code being searched for * @return {@code true} if an error with the given error code has been gathered */ public boolean hasError(ErrorCode errorCode) { for (AnalysisError error : errors) { if (error.getErrorCode() == errorCode) { return true; } } return false; } /** * Return {@code true} if at least one error has been gathered. * * @return {@code true} if at least one error has been gathered */ public boolean hasErrors() { return errors.size() > 0; } @Override public void onError(AnalysisError error) { if (rawSource != null) { int left = error.getOffset(); int right = left + error.getLength() - 1; markedSource = rawSource.substring(0, left) + "^" + rawSource.substring(left, right) + "^" + rawSource.substring(right); } errors.add(error); } /** * Set the line information associated with the given source to the given information. * * @param source the source with which the line information is associated * @param lineStarts the line start information to be associated with the source */ public void setLineInfo(Source source, int[] lineStarts) { lineInfoMap.put(source, new LineInfo(lineStarts)); } /** * Return {@code true} if the two errors are equivalent. * * @param firstError the first error being compared * @param secondError the second error being compared * @return {@code true} if the two errors are equivalent */ private boolean equalErrors(AnalysisError firstError, AnalysisError secondError) { return firstError.getErrorCode() == secondError.getErrorCode() && firstError.getOffset() == secondError.getOffset() && firstError.getLength() == secondError.getLength() && equalSources(firstError.getSource(), secondError.getSource()); } /** * Return {@code true} if the two sources are equivalent. * * @param firstSource the first source being compared * @param secondSource the second source being compared * @return {@code true} if the two sources are equivalent */ private boolean equalSources(Source firstSource, Source secondSource) { if (firstSource == null) { return secondSource == null; } else if (secondSource == null) { return false; } return firstSource.equals(secondSource); } /** * Assert that the number of errors that have been gathered matches the number of errors that are * given and that they have the expected error codes. The order in which the errors were gathered * is ignored. * * @param errorCodes the errors that should have been gathered * @throws AssertionFailedError with */ private void fail(AnalysisError[] expectedErrors) { @SuppressWarnings("resource") PrintStringWriter writer = new PrintStringWriter(); writer.print("Expected "); writer.print(expectedErrors.length); writer.print(" errors:"); for (AnalysisError error : expectedErrors) { Source source = error.getSource(); LineInfo lineInfo = lineInfoMap.get(source); writer.println(); if (lineInfo == null) { int offset = error.getOffset(); writer.printf( " %s %s (%d..%d)", source == null ? "" : source.getShortName(), error.getErrorCode(), offset, offset + error.getLength()); } else { LineInfo.Location location = lineInfo.getLocation(error.getOffset()); writer.printf( " %s %s (%d, %d/%d)", source == null ? "" : source.getShortName(), error.getErrorCode(), location.getLineNumber(), location.getColumnNumber(), error.getLength()); } } writer.println(); writer.print("found "); writer.print(errors.size()); writer.print(" errors:"); for (AnalysisError error : errors) { Source source = error.getSource(); LineInfo lineInfo = lineInfoMap.get(source); writer.println(); if (lineInfo == null) { int offset = error.getOffset(); writer.printf( " %s %s (%d..%d): %s", source == null ? "" : source.getShortName(), error.getErrorCode(), offset, offset + error.getLength(), error.getMessage()); } else { LineInfo.Location location = lineInfo.getLocation(error.getOffset()); writer.printf( " %s %s (%d, %d/%d): %s", source == null ? "" : source.getShortName(), error.getErrorCode(), location.getLineNumber(), location.getColumnNumber(), error.getLength(), error.getMessage()); } } Assert.fail(writer.toString()); } /** * Search through the given list of errors for an error that is equal to the target error. If one * is found, remove it from the list and return {@code true}, otherwise return {@code false} * without modifying the list. * * @param errors the errors through which we are searching * @param targetError the error being searched for * @return {@code true} if the error is found and removed from the list */ private boolean foundAndRemoved(List<AnalysisError> errors, AnalysisError targetError) { for (AnalysisError error : errors) { if (equalErrors(error, targetError)) { errors.remove(error); return true; } } return false; } }