blob: 46654c3a76a7c9fa8f8cfefa5db8ccfa00577418 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler.parser;
import com.google.common.collect.Lists;
import com.google.dart.compiler.DartCompilerListener;
import com.google.dart.compiler.Source;
import com.google.dart.compiler.ast.ASTVisitor;
import com.google.dart.compiler.ast.DartComment;
import com.google.dart.compiler.ast.DartCommentNewName;
import com.google.dart.compiler.ast.DartCommentRefName;
import com.google.dart.compiler.ast.DartDeclaration;
import com.google.dart.compiler.ast.DartField;
import com.google.dart.compiler.ast.DartMethodDefinition;
import com.google.dart.compiler.ast.DartNode;
import com.google.dart.compiler.ast.DartUnit;
import com.google.dart.compiler.common.SourceInfo;
import com.google.dart.compiler.metrics.CompilerMetrics;
import com.google.dart.compiler.util.apache.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* A parser for Dart that records comment positions.
*/
public class DartParserCommentsHelper {
static class CommentParserContext extends DartScannerParserContext {
private List<int[]> commentLocs;
CommentParserContext(Source source, String code, DartCompilerListener listener,
CompilerMetrics metrics) {
super(source, code, listener, metrics);
}
List<int[]> getCommentLocs() {
return commentLocs;
}
@Override
protected DartScanner createScanner(String sourceCode, Source source,
DartCompilerListener listener) {
commentLocs = Lists.newArrayList();
return new CommentScanner(sourceCode, 0, source, listener);
}
private class CommentScanner extends DartScanner {
CommentScanner(String sourceCode, int start, Source sourceReference,
DartCompilerListener listener) {
super(sourceCode, start, sourceReference, listener);
}
@Override
protected void recordCommentLocation(int start, int stop) {
int size = commentLocs.size();
if (size > 0) {
// the parser may re-scan lookahead tokens
// fortunately, comments are always scanned as comments
int[] loc = commentLocs.get(size - 1);
if (start <= loc[0] && stop <= loc[1]) {
return;
}
}
commentLocs.add(new int[] {start, stop});
}
}
}
static void addComments(DartUnit unit, Source source, String sourceCode, List<int[]> commentLocs) {
for (int[] loc : commentLocs) {
int start = loc[0];
int length = loc[1] - start;
DartComment.Style style = getCommentStyle(sourceCode, start);
unit.getComments().add(new DartComment(source, start, length, style));
}
List<DartComment> comments = unit.getComments();
if (comments != null) {
assignDartComments(unit, sourceCode, comments);
}
}
private static void assignDartComments(DartUnit unit, String sourceCode,
List<DartComment> comments) {
// Collect the AST nodes in a list.
final List<DartNode> astNodes = new ArrayList<DartNode>();
unit.accept(new ASTVisitor<DartNode>() {
@Override
public DartNode visitDeclaration(DartDeclaration<?> node) {
astNodes.add(node);
// Avoid NPE in visitors because of missing part.
try {
super.visitDeclaration(node);
} catch (NullPointerException e) {
}
// No result.
return null;
}
});
// Collect all the nodes in one list.
List<DartNode> nodes = new ArrayList<DartNode>();
nodes.addAll(comments);
nodes.addAll(astNodes);
// Sort the nodes by their position in the source file.
Collections.sort(nodes, new Comparator<DartNode>() {
@Override
public int compare(DartNode node1, DartNode node2) {
return node1.getSourceInfo().getOffset() - node2.getSourceInfo().getOffset();
}
});
// Assign dart docs to their associated DartDeclarations.
for (int i = 0; i < nodes.size(); i++) {
DartNode node = nodes.get(i);
if (node instanceof DartComment) {
DartComment comment = (DartComment) node;
// prepare next declaration
DartDeclaration<?> decl = null;
{
int delta = 1;
while (i + delta < nodes.size()) {
DartNode next = nodes.get(i + delta);
// skip all comments
if (next instanceof DartComment) {
delta++;
continue;
}
// declaration found
if (next instanceof DartDeclaration) {
decl = (DartDeclaration<?>) next;
if (!commentContainedBySibling(comment, decl)) {
if (i + 2 < nodes.size()) {
decl = adjustDartdocTarget(next, nodes.get(i + 2));
}
}
break;
}
// something other than declaration
break;
}
}
// apply comment to declaration
if (decl != null) {
String commentStr = sourceCode.substring(comment.getSourceInfo().getOffset(),
comment.getSourceInfo().getEnd());
tokenizeComment(comment, commentStr);
// DartDoc
if (comment.isDartDoc()) {
decl.setDartDoc(comment);
}
}
}
}
}
private static void tokenizeComment(DartComment comment, String src) {
int lastIndex = 0;
while (true) {
int openIndex = src.indexOf('[', lastIndex);
if (openIndex == -1) {
break;
}
int closeIndex = src.indexOf(']', openIndex);
if (closeIndex == -1) {
break;
}
lastIndex = closeIndex;
String tokenSrc = src.substring(openIndex + 1, closeIndex);
if (tokenSrc.startsWith(":") && tokenSrc.endsWith(":")) {
// TODO(scheglov) [:code:] and 'code'
} else if (tokenSrc.startsWith("new ")) {
SourceInfo sourceInfo = comment.getSourceInfo();
int offset = sourceInfo.getOffset() + openIndex;
int classOffset = offset + "[".length();
// remove leading "new "
String name = StringUtils.remove(tokenSrc, "new ");
classOffset += "new ".length();
// remove spaces
{
String stripName = StringUtils.stripStart(name, null);
classOffset += name.length() - stripName.length();
name = stripName;
}
name = name.trim();
//
String className = StringUtils.substringBefore(name, ".");
String constructorName = StringUtils.substringAfter(name, ".");
int constructorOffset = classOffset + className.length() + ".".length();
DartCommentNewName newNode = new DartCommentNewName(className, classOffset,
constructorName, constructorOffset);
{
Source source = sourceInfo.getSource();
int length = tokenSrc.length() + "[]".length();
newNode.setSourceInfo(new SourceInfo(source, offset, length));
}
// add node
comment.addNewName(newNode);
} else {
String name = tokenSrc.trim();
DartCommentRefName refNode = new DartCommentRefName(name);
{
SourceInfo sourceInfo = comment.getSourceInfo();
Source source = sourceInfo.getSource();
int offset = sourceInfo.getOffset() + openIndex;
int length = name.length() + "[]".length();
refNode.setSourceInfo(new SourceInfo(source, offset, length));
}
comment.addRefName(refNode);
}
}
}
private static DartDeclaration<?> adjustDartdocTarget(DartNode currentNode, DartNode nextNode) {
if (currentNode instanceof DartField && nextNode instanceof DartMethodDefinition) {
if (currentNode.getSourceInfo().equals(nextNode.getSourceInfo())) {
return (DartDeclaration<?>) nextNode;
}
}
return (DartDeclaration<?>) currentNode;
}
/**
* DartC creates both a {@link DartField} and a {@link DartMethodDefinition} for getters and
* setters. They have the same source location; we want to assign the DartDoc to the method
* definition and not the field.
*/
private static boolean commentContainedBySibling(DartComment comment, DartDeclaration<?> node) {
if (node instanceof DartField) {
for (DartNode child : getChildren(node.getParent())) {
if (child != node && !(child instanceof DartComment)) {
if (isContainedBy(comment, child)) {
return true;
}
}
}
}
return false;
}
private static List<DartNode> getChildren(DartNode parent) {
final List<DartNode> children = new ArrayList<DartNode>();
parent.visitChildren(new ASTVisitor<DartNode>() {
@Override
public DartNode visitNode(DartNode node) {
children.add(node);
return null;
}
});
return children;
}
private static boolean isContainedBy(DartNode node, DartNode containedByNode) {
SourceInfo nodeSource = node.getSourceInfo();
SourceInfo containedBySource = containedByNode.getSourceInfo();
int nodeEnd = nodeSource.getOffset() + nodeSource.getLength();
int containedByEnd = containedBySource.getOffset() + containedBySource.getLength();
return nodeSource.getOffset() >= containedBySource.getOffset() && nodeEnd <= containedByEnd;
}
/**
* Return the style of the comment in the given string.
*
* @param sourceString the source containing the comment
* @param commentStart the location of the comment in the source
* @return the style of the comment in the given string
*/
private static DartComment.Style getCommentStyle(String sourceString, int commentStart) {
boolean hasMore1 = commentStart + 1 < sourceString.length();
boolean hasMore2 = commentStart + 2 < sourceString.length();
if (hasMore1 && sourceString.charAt(commentStart + 1) == '/') {
return DartComment.Style.END_OF_LINE;
} else if (hasMore2 && sourceString.charAt(commentStart + 2) == '*') {
return DartComment.Style.DART_DOC;
}
return DartComment.Style.BLOCK;
}
}