/* * 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.dev.util.ast; import com.google.dart.dev.util.DartDevPlugin; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.BinaryExpression; import com.google.dart.engine.ast.ClassDeclaration; import com.google.dart.engine.ast.ClassTypeAlias; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.ConstructorDeclaration; import com.google.dart.engine.ast.ConstructorName; import com.google.dart.engine.ast.ExportDirective; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.FieldDeclaration; import com.google.dart.engine.ast.FunctionDeclaration; import com.google.dart.engine.ast.FunctionExpressionInvocation; import com.google.dart.engine.ast.FunctionTypeAlias; import com.google.dart.engine.ast.Identifier; import com.google.dart.engine.ast.ImportDirective; import com.google.dart.engine.ast.LibraryDirective; import com.google.dart.engine.ast.MethodDeclaration; import com.google.dart.engine.ast.PartDirective; import com.google.dart.engine.ast.PartOfDirective; import com.google.dart.engine.ast.PostfixExpression; import com.google.dart.engine.ast.PrefixExpression; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.SimpleStringLiteral; import com.google.dart.engine.ast.TopLevelVariableDeclaration; import com.google.dart.engine.ast.TypeName; import com.google.dart.engine.ast.TypeParameter; import com.google.dart.engine.ast.VariableDeclaration; import com.google.dart.engine.ast.VariableDeclarationList; import com.google.dart.engine.element.Element; import com.google.dart.engine.error.AnalysisError; import com.google.dart.engine.error.AnalysisErrorListener; import com.google.dart.engine.parser.Parser; import com.google.dart.engine.scanner.CharSequenceReader; import com.google.dart.engine.scanner.Scanner; import com.google.dart.engine.scanner.Token; import com.google.dart.engine.type.Type; import com.google.dart.tools.core.utilities.io.FileUtilities; import com.google.dart.tools.core.utilities.resource.IFileUtilities; import com.google.dart.tools.ui.internal.text.editor.CompilationUnitEditor; import com.google.dart.tools.ui.internal.text.editor.DartEditor; import com.google.dart.tools.ui.internal.text.editor.EditorUtility; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.ISelectionService; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.ide.FileStoreEditorInput; import org.eclipse.ui.part.ViewPart; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; /** * A basic AST explorer view. */ @SuppressWarnings("restriction") public class ASTExplorer extends ViewPart implements AnalysisErrorListener { private class EventHandler implements IPartListener, ISelectionListener, ISelectionChangedListener { //triggers refresh on save and updates label to indicate dirtiness private IPropertyListener propertyListener = new IPropertyListener() { @Override public void propertyChanged(Object source, int propId) { if (source instanceof DartEditor) { if (propId == IEditorPart.PROP_DIRTY) { if (((DartEditor) source).isDirty()) { setPartName('*' + getPartName()); } else { refresh(); String name = getPartName(); if (name.charAt(0) == '*') { name = name.substring(1); } setPartName(name); } } } } }; private final List<IWorkbenchPart> parts = new ArrayList<IWorkbenchPart>(); @Override public void partActivated(IWorkbenchPart part) { if (part instanceof DartEditor) { part.addPropertyListener(propertyListener); parts.add(part); } } @Override public void partBroughtToTop(IWorkbenchPart part) { //ignore } @Override public void partClosed(IWorkbenchPart part) { if (part instanceof DartEditor) { part.removePropertyListener(propertyListener); parts.remove(part); } } @Override public void partDeactivated(IWorkbenchPart part) { if (part instanceof DartEditor) { part.removePropertyListener(propertyListener); parts.remove(part); } } @Override public void partOpened(IWorkbenchPart part) { //ignore } @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { //TODO(pquitslund): implement text selection tracking if (selection instanceof ITextSelection) { int offset = ((ITextSelection) selection).getOffset(); int length = ((ITextSelection) selection).getLength(); selectNodeAtOffset(offset, length); } } @Override public void selectionChanged(SelectionChangedEvent event) { IEditorPart editor = getActiveEditor(); if (editor instanceof DartEditor) { ISelection selection = event.getSelection(); if (selection instanceof TreeSelection) { Object element = ((TreeSelection) selection).getFirstElement(); if (element instanceof AstNode) { AstNode node = (AstNode) element; EditorUtility.revealInEditor(editor, node.getOffset(), node.getLength()); } if (element instanceof AnalysisError) { AnalysisError error = (AnalysisError) element; EditorUtility.revealInEditor(editor, error.getOffset(), error.getLength()); } tableViewer.setInput(element); } } } void dispose() { getSelectionService().removeSelectionListener(this); getPage().removePartListener(this); for (IWorkbenchPart part : parts) { part.removePropertyListener(propertyListener); } } } private class ExplorerContentProvider implements IStructuredContentProvider, ITreeContentProvider { private final AstNode[] NO_NODES = new AstNode[0]; @Override public void dispose() { } @Override public Object[] getChildren(Object parent) { if (parent instanceof AstNode) { AstNode node = (AstNode) parent; CollectingVisitor nodeCollector = new CollectingVisitor(); node.visitChildren(nodeCollector); return nodes(nodeCollector, parent); } return NO_NODES; } @Override public Object[] getElements(Object parent) { // TODO(brianwilkerson) Figure out how to get the CompilationUnit to be the top-level node in // the tree rather than it's children. The commented out code causes an infinite recursion (as // in, the unit appears to have one child, which is the unit, which has one child, etc.). return getChildren(parent); // new Object[] {parent}; } @Override public Object getParent(Object child) { if (child instanceof AstNode) { return ((AstNode) child).getParent(); } return null; } @Override public boolean hasChildren(Object parent) { return getChildren(parent).length > 0; } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { viewer.getControl().setRedraw(false); ((TreeViewer) viewer).refresh(true); viewer.getControl().setRedraw(true); } private Object[] nodes(CollectingVisitor nodeCollector, Object object) { ArrayList<Object> children = new ArrayList<Object>(nodeCollector.getNodes()); if (object instanceof CompilationUnit) { children.addAll(errors); } return children.toArray(); } } private static class ExplorerLabelProvider extends LabelProvider { @Override public Image getImage(Object obj) { String img; if (obj instanceof AnalysisError) { img = "error_obj.gif"; } else if (obj instanceof Element) { // TODO(brianwilkerson) Find a better icon for this img = "class_hi.gif"; } else { img = "brkpi_obj.gif"; } return DartDevPlugin.getImage(img); } @Override public String getText(Object obj) { if (obj instanceof AnalysisError) { return ((AnalysisError) obj).getMessage(); } StringBuilder builder = new StringBuilder(); builder.append(obj.getClass().getSimpleName()); if (obj instanceof AstNode) { AstNode node = (AstNode) obj; // builder.append(" ["); // builder.append(node.getOffset()); // builder.append(".."); // builder.append(node.getOffset() + node.getLength() - 1); // builder.append("]"); String name = getName(node); if (name != null) { builder.append(" - "); builder.append(name); } // if (obj instanceof Expression) { // builder.append(" ("); // Type staticType = ((Expression) obj).getStaticType(); // if (staticType == null) { // builder.append("null"); // } else { // builder.append(staticType); // } // builder.append("/"); // Type propagatedType = ((Expression) obj).getPropagatedType(); // if (propagatedType == null) { // builder.append("null"); // } else { // builder.append(propagatedType); // } // builder.append(")"); // } // } else if (obj instanceof Element) { // String name = ((Element) obj).getDisplayName(); // if (name != null) { // builder.append(" - "); // builder.append(name); // } } return builder.toString(); } /** * Return the name of the given node, or {@code null} if the given node is not a declaration. * * @param node the node whose name is to be returned * @return the name of the given node */ private String getName(AstNode node) { // TODO(brianwilkerson) Rewrite this to use a visitor. if (node instanceof ClassTypeAlias) { return ((ClassTypeAlias) node).getName().getName(); } else if (node instanceof ClassDeclaration) { return ((ClassDeclaration) node).getName().getName(); } else if (node instanceof ConstructorDeclaration) { ConstructorDeclaration cd = (ConstructorDeclaration) node; if (cd.getName() == null) { return cd.getReturnType().getName(); } else { return cd.getReturnType().getName() + '.' + cd.getName().getName(); } } else if (node instanceof ConstructorName) { return ((ConstructorName) node).toSource(); } else if (node instanceof FieldDeclaration) { return getNames(((FieldDeclaration) node).getFields()); } else if (node instanceof FunctionDeclaration) { SimpleIdentifier nameNode = ((FunctionDeclaration) node).getName(); if (nameNode != null) { return nameNode.getName(); } } else if (node instanceof FunctionTypeAlias) { return ((FunctionTypeAlias) node).getName().getName(); } else if (node instanceof Identifier) { return ((Identifier) node).getName(); } else if (node instanceof MethodDeclaration) { return ((MethodDeclaration) node).getName().getName(); } else if (node instanceof TopLevelVariableDeclaration) { return getNames(((TopLevelVariableDeclaration) node).getVariables()); } else if (node instanceof TypeName) { return ((TypeName) node).toSource(); } else if (node instanceof TypeParameter) { return ((TypeParameter) node).getName().getName(); } else if (node instanceof VariableDeclaration) { return ((VariableDeclaration) node).getName().getName(); } return null; } /** * Return a string containing a comma-separated list of the names of all of the variables in the * given list. * * @param variables the list containing the variables * @return a comma-separated list of the names of the given variables */ private String getNames(VariableDeclarationList variables) { boolean first = true; StringBuilder builder = new StringBuilder(); for (VariableDeclaration variable : variables.getVariables()) { if (first) { first = false; } else { builder.append(", "); } builder.append(variable.getName().getName()); } return builder.toString(); } } private static class PropertiesContentProvider implements IStructuredContentProvider { private static Object[] NO_ELEMENTS = new Object[0]; @Override public void dispose() { } @Override public Object[] getElements(Object inputElement) { HashMap<String, String> propertyMap = new HashMap<String, String>(); addProperties(propertyMap, inputElement); if (propertyMap.isEmpty()) { return NO_ELEMENTS; } int count = propertyMap.size(); String[] names = propertyMap.keySet().toArray(new String[count]); Arrays.sort(names); String[][] elements = new String[count][]; for (int i = 0; i < count; i++) { String name = names[i]; elements[i] = new String[] {name, propertyMap.get(name)}; } return elements; } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } private void addProperties(HashMap<String, String> propertyMap, Object inputElement) { if (inputElement instanceof AstNode) { AstNode node = (AstNode) inputElement; propertyMap.put("offset", Integer.toString(node.getOffset())); propertyMap.put("length", Integer.toString(node.getLength())); } if (inputElement instanceof Expression) { Expression expression = (Expression) inputElement; propertyMap.put("staticType", toString(expression.getStaticType())); propertyMap.put("propagatedType", toString(expression.getPropagatedType())); } if (inputElement instanceof BinaryExpression) { BinaryExpression expression = (BinaryExpression) inputElement; propertyMap.put("staticElement", toString(expression.getStaticElement())); propertyMap.put("propagatedElement", toString(expression.getPropagatedElement())); } else if (inputElement instanceof CompilationUnit) { CompilationUnit unit = (CompilationUnit) inputElement; propertyMap.put("element", toString(unit.getElement())); } else if (inputElement instanceof ExportDirective) { ExportDirective directive = (ExportDirective) inputElement; propertyMap.put("element", toString(directive.getElement())); } else if (inputElement instanceof FunctionExpressionInvocation) { FunctionExpressionInvocation expression = (FunctionExpressionInvocation) inputElement; propertyMap.put("staticElement", toString(expression.getStaticElement())); propertyMap.put("propagatedElement", toString(expression.getPropagatedElement())); } else if (inputElement instanceof ImportDirective) { ImportDirective directive = (ImportDirective) inputElement; propertyMap.put("element", toString(directive.getElement())); } else if (inputElement instanceof LibraryDirective) { LibraryDirective directive = (LibraryDirective) inputElement; propertyMap.put("element", toString(directive.getElement())); } else if (inputElement instanceof PartDirective) { PartDirective directive = (PartDirective) inputElement; propertyMap.put("element", toString(directive.getElement())); } else if (inputElement instanceof PartOfDirective) { PartOfDirective directive = (PartOfDirective) inputElement; propertyMap.put("element", toString(directive.getElement())); } else if (inputElement instanceof PostfixExpression) { PostfixExpression expression = (PostfixExpression) inputElement; propertyMap.put("staticElement", toString(expression.getStaticElement())); propertyMap.put("propagatedElement", toString(expression.getPropagatedElement())); } else if (inputElement instanceof PrefixExpression) { PrefixExpression expression = (PrefixExpression) inputElement; propertyMap.put("staticElement", toString(expression.getStaticElement())); propertyMap.put("propagatedElement", toString(expression.getPropagatedElement())); } else if (inputElement instanceof SimpleIdentifier) { SimpleIdentifier identifier = (SimpleIdentifier) inputElement; propertyMap.put("staticElement", toString(identifier.getStaticElement())); propertyMap.put("propagatedElement", toString(identifier.getPropagatedElement())); } else if (inputElement instanceof SimpleStringLiteral) { propertyMap.put("value", toString(((SimpleStringLiteral) inputElement).getValue())); } } private String toString(Element element) { if (element == null) { return "null"; } String name = element.getDisplayName(); if (name == null) { name = "- unnamed -"; } return name + " (" + element.getClass().getSimpleName() + ")"; } private String toString(String string) { if (string == null) { return "null"; } return string; } private String toString(Type type) { if (type == null) { return "null"; } String name = type.getDisplayName(); if (name == null) { return "- unnamed -"; } return name; } } private static class PropertiesLabelProvider implements ITableLabelProvider { @Override public void addListener(ILabelProviderListener listener) { } @Override public void dispose() { } @Override public Image getColumnImage(Object element, int columnIndex) { return null; } @Override public String getColumnText(Object element, int columnIndex) { if (element instanceof String[]) { return ((String[]) element)[columnIndex]; } return null; } @Override public boolean isLabelProperty(Object element, String property) { return false; } @Override public void removeListener(ILabelProviderListener listener) { } } /** * View extension id. */ public static final String ID = "com.google.dart.dev.util.ast.ASTExplorer"; private TreeViewer treeViewer; private TableViewer tableViewer; private Action expandAllAction; private Action collapseAllAction; private Action refreshAction; private EventHandler eventHandler = new EventHandler(); private ArrayList<AnalysisError> errors = new ArrayList<AnalysisError>(); @Override public void createPartControl(Composite parent) { // TODO(brianwilkerson) It would be nice to have a slider between the tree and the table. parent.setLayout(new FillLayout(SWT.VERTICAL)); treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); treeViewer.setContentProvider(new ExplorerContentProvider()); treeViewer.setLabelProvider(new ExplorerLabelProvider()); tableViewer = new TableViewer(parent); tableViewer.setContentProvider(new PropertiesContentProvider()); tableViewer.setLabelProvider(new PropertiesLabelProvider()); tableViewer.getTable().setHeaderVisible(true); TableColumn nameColumn = new TableColumn(tableViewer.getTable(), SWT.LEFT); nameColumn.setResizable(true); nameColumn.setText("Name"); nameColumn.setWidth(150); TableColumn valueColumn = new TableColumn(tableViewer.getTable(), SWT.LEFT); valueColumn.setResizable(true); valueColumn.setText("Value"); valueColumn.setWidth(600); makeActions(); contributeToActionBars(); hookupListeners(); refresh(); } @Override public void dispose() { eventHandler.dispose(); } @Override public void onError(AnalysisError error) { errors.add(error); } @Override public void setFocus() { refresh(); treeViewer.getControl().setFocus(); } protected IEditorPart getActiveEditor() { return getPage().getActiveEditor(); } protected IWorkbenchPage getPage() { return getSite().getPage(); } protected ISelectionService getSelectionService() { return (ISelectionService) getSite().getService(ISelectionService.class); } private void contributeToActionBars() { IActionBars bars = getViewSite().getActionBars(); contributeViewToolItems(bars.getToolBarManager()); } private void contributeViewToolItems(IToolBarManager manager) { manager.add(expandAllAction); manager.add(collapseAllAction); manager.add(refreshAction); } private CompilationUnit getCompilationUnit() { IEditorPart editor = getActiveEditor(); if (editor instanceof CompilationUnitEditor) { CompilationUnit unit = ((CompilationUnitEditor) editor).getInputUnit(); if (unit != null) { return unit; } } if (editor != null) { IEditorInput input = editor.getEditorInput(); if (input instanceof IFileEditorInput || input instanceof FileStoreEditorInput) { try { String contents = ""; if (input instanceof IFileEditorInput) { IFile file = ((IFileEditorInput) input).getFile(); contents = IFileUtilities.getContents(file); } else { URI uri = ((FileStoreEditorInput) input).getURI(); contents = FileUtilities.getDartContents(new File(uri)); } errors.clear(); Scanner scanner = new Scanner(null, new CharSequenceReader(contents), this); Parser parser = new Parser(null, this); Token token = scanner.tokenize(); return parser.parseCompilationUnit(token); } catch (CoreException e) { DartDevPlugin.logError(e); } catch (IOException e) { DartDevPlugin.logError(e); } } } return null; } /** * Return the element associated with the given AST node, or {@code null} if there is no element. * * @param object the AST node whose associated element is to be returned * @return the element associated with the given AST node */ private Element getElement(Object object) { if (object instanceof BinaryExpression) { return ((BinaryExpression) object).getStaticElement(); } else if (object instanceof CompilationUnit) { return ((CompilationUnit) object).getElement(); } else if (object instanceof ExportDirective) { return ((ExportDirective) object).getElement(); } else if (object instanceof FunctionExpressionInvocation) { return ((FunctionExpressionInvocation) object).getStaticElement(); } else if (object instanceof ImportDirective) { return ((ImportDirective) object).getElement(); } else if (object instanceof LibraryDirective) { return ((LibraryDirective) object).getElement(); } else if (object instanceof PartDirective) { return ((PartDirective) object).getElement(); } else if (object instanceof PartOfDirective) { return ((PartOfDirective) object).getElement(); } else if (object instanceof PostfixExpression) { return ((PostfixExpression) object).getStaticElement(); } else if (object instanceof PrefixExpression) { return ((PrefixExpression) object).getStaticElement(); } else if (object instanceof SimpleIdentifier) { return ((SimpleIdentifier) object).getBestElement(); } return null; } private void hookupListeners() { treeViewer.addSelectionChangedListener(eventHandler); getSelectionService().addSelectionListener(eventHandler); getPage().addPartListener(eventHandler); } private void makeActions() { expandAllAction = new Action() { @Override public void run() { if (!treeViewer.getControl().isDisposed()) { treeViewer.getControl().setRedraw(false); treeViewer.expandToLevel(treeViewer.getInput(), AbstractTreeViewer.ALL_LEVELS); treeViewer.getControl().setRedraw(true); } } }; expandAllAction.setText("Expand All"); expandAllAction.setToolTipText("Expand All"); expandAllAction.setImageDescriptor(DartDevPlugin.getImageDescriptor("expandall.gif")); collapseAllAction = new Action() { @Override public void run() { if (!treeViewer.getControl().isDisposed()) { treeViewer.getControl().setRedraw(false); treeViewer.collapseToLevel(treeViewer.getInput(), AbstractTreeViewer.ALL_LEVELS); treeViewer.getControl().setRedraw(true); } } }; collapseAllAction.setText("Collapse All"); collapseAllAction.setToolTipText("Collapse All"); collapseAllAction.setImageDescriptor(DartDevPlugin.getImageDescriptor("collapseall.gif")); refreshAction = new Action() { @Override public void run() { refresh(); } }; refreshAction.setText("Refresh"); refreshAction.setToolTipText("Refresh"); refreshAction.setImageDescriptor(DartDevPlugin.getImageDescriptor("refresh.gif")); } private void refresh() { treeViewer.setInput(getCompilationUnit()); } private void selectNodeAtOffset(int offset, int length) { //TODO(pquitslund): implement if (length <= 0) { return; } // NodeLocator locator = new NodeLocator(offset, offset + length - 1); // ASTNode node = locator.searchWithin(getCompilationUnit()); // viewer.setSelection(new StructuredSelection(node), true); } }