blob: aa648e6c7ba69d7137f65a4159ff039cfbe5a4aa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 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.javascript.parser.jsdoc;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.dltk.javascript.ast.MultiLineComment;
import org.eclipse.dltk.utils.IntList;
public class SimpleJSDocParser {
private static final char FORM_FEED = '\u000c';
private static final char CR = '\r';
private static final char LF = '\n';
private static final char TAB = '\t';
private static final char SPACE = ' ';
private char buffer[];
private int index;
private int end;
private final StringBuilder value = new StringBuilder();
private final IntList ranges = new IntList();
public JSDocTags parse(String content, int offset) {
List<JSDocTag> tags = null;
index = MultiLineComment.JSDOC_PREFIX.length();
buffer = content.toCharArray();
end = buffer.length;
if (index + 2 <= end && buffer[end - 2] == '*'
&& buffer[end - 1] == '/') {
end -= 2;
}
while (index < end) {
switch (readChar()) {
case '*':
case SPACE:
case TAB:
case FORM_FEED:
continue;
case LF:
skipChar(CR);
continue;
case CR:
skipChar(LF);
continue;
case '@':
final JSDocTag tag = parseTag(offset);
if (tag != null) {
if (tags == null) {
tags = new ArrayList<JSDocTag>();
}
tags.add(tag);
} else {
skipEndOfLine();
}
break;
default:
skipEndOfLine();
continue;
}
}
if (tags != null) {
return new JSDocTags(tags.toArray(new JSDocTag[tags.size()]));
} else {
return JSDocTags.EMPTY;
}
}
private JSDocTag parseTag(int offset) {
final int tagStart = index - 1;
if (index < end && Character.isLetter(buffer[index])) {
++index;
while (index < end
&& (buffer[index] == '.' || buffer[index] == '-' || Character
.isLetterOrDigit(buffer[index]))) {
++index;
}
}
if (index == tagStart + 1) {
return null;
}
final String tag = new String(buffer, tagStart, index - tagStart);
final int nameEnd = index;
value.setLength(0);
ranges.clear();
skipSpaces();
boolean lineStart = false;
VALUE_LOOP: while (index < end) {
char c = readChar();
switch (c) {
case '@':
if (lineStart) {
unread();
break VALUE_LOOP;
}
value.append(c);
addToRanges(index);
break;
case CR:
skipChar(LF);
lineStart = true;
skipSpaces();
if (skipAll('*') && skipChar('/')) {
// end of comment
break VALUE_LOOP;
}
break;
case LF:
skipChar(CR);
lineStart = true;
skipSpaces();
if (skipAll('*') && skipChar('/')) {
// end of comment
break VALUE_LOOP;
}
break;
case SPACE:
case TAB:
case FORM_FEED:
value.append(c);
addToRanges(index);
break;
default:
lineStart = false;
value.append(c);
addToRanges(index);
break;
}
}
int len = value.length();
while (len > 0 && Character.isWhitespace(value.charAt(len - 1))) {
--len;
}
if (len != value.length()) {
trimRangesBy(value.length() - len);
value.setLength(len);
}
final int valueStart;
final int end;
final String value;
final int[] ranges;
if (this.ranges.isEmpty()) {
valueStart = nameEnd;
end = nameEnd;
value = "";
ranges = null;
} else {
valueStart = this.ranges.first();
end = this.ranges.last();
value = this.value.toString();
if (end - valueStart == value.length()) {
ranges = null;
} else {
ranges = this.ranges.toArray();
for (int i = 0; i < ranges.length; ++i) {
ranges[i] += offset;
}
}
}
return new JSDocTag(tag, value, offset + tagStart, offset + valueStart,
offset + end, ranges);
}
private void trimRangesBy(int delta) {
if (!ranges.isEmpty()) {
int endIndex = ranges.size();
while (endIndex > 0) {
int rangeLen = ranges.get(endIndex - 1)
- ranges.get(endIndex - 2);
if (rangeLen <= delta) {
delta -= rangeLen;
endIndex -= 2;
} else {
ranges.set(endIndex - 1, ranges.get(endIndex - 1) - delta);
break;
}
}
if (endIndex != ranges.size()) {
ranges.setSize(endIndex);
}
}
}
private void addToRanges(int offset) {
if (ranges.isEmpty() || ranges.last() != offset - 1) {
ranges.add(offset - 1);
ranges.add(offset);
} else {
ranges.set(ranges.size() - 1, offset);
}
}
private boolean skipAll(char expected) {
boolean result = false;
while (index < end && buffer[index] == expected) {
++index;
result = true;
}
return result;
}
private void skipSpaces() {
while (index < end && (buffer[index] == ' ' || buffer[index] == '\t')) {
++index;
}
}
private void skipEndOfLine() {
LOOP: while (index < end) {
switch (readChar()) {
case CR:
skipChar(LF);
break LOOP;
case LF:
skipChar(CR);
break LOOP;
}
}
}
private boolean skipChar(char expected) {
if (index < end && buffer[index] == expected) {
++index;
return true;
}
return false;
}
private void unread() {
--index;
}
private char readChar() {
return buffer[index++];
}
}