blob: 6d2755890d48b94d2363d042362945188c0fb614 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 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 (Vladimir Belov)
*******************************************************************************/
package org.eclipse.dltk.javascript.formatter;
import java.util.List;
import java.util.Map;
import org.eclipse.dltk.compiler.problem.IProblem;
import org.eclipse.dltk.compiler.problem.ProblemCollector;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.formatter.AbstractScriptFormatter;
import org.eclipse.dltk.formatter.FormatterDocument;
import org.eclipse.dltk.formatter.FormatterIndentDetector;
import org.eclipse.dltk.formatter.IFormatterContainerNode;
import org.eclipse.dltk.formatter.IFormatterContext;
import org.eclipse.dltk.javascript.ast.Script;
import org.eclipse.dltk.javascript.formatter.internal.FormatterNodeBuilder;
import org.eclipse.dltk.javascript.formatter.internal.JavaScriptFormatterContext;
import org.eclipse.dltk.javascript.formatter.internal.JavaScriptFormatterWriter;
import org.eclipse.dltk.javascript.formatter.internal.JavascriptFormatterNodeRewriter;
import org.eclipse.dltk.javascript.parser.JSProblem;
import org.eclipse.dltk.javascript.parser.JavaScriptParser;
import org.eclipse.dltk.javascript.parser.JavaScriptParserProblems;
import org.eclipse.dltk.ui.formatter.FormatterException;
import org.eclipse.dltk.ui.formatter.FormatterSyntaxProblemException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
public class JavaScriptFormatter extends AbstractScriptFormatter {
private final String lineDelimiter;
public JavaScriptFormatter(String lineDelimiter,
Map<String, ? extends Object> preferences) {
super(preferences);
this.lineDelimiter = lineDelimiter;
}
public TextEdit format(String source, int offset, int length,
int indentationLevel) throws FormatterException {
String input = source.substring(offset, offset + length);
String formatted = format(input, indentationLevel);
if (!input.equals(formatted)) {
return new ReplaceEdit(offset, length, formatted);
} else {
return new MultiTextEdit(); // NOP
}
}
private static class ParserProblemReporter extends ProblemCollector {
@Override
public boolean hasErrors() {
if (!problems.isEmpty()) {
for (final IProblem problem : problems) {
if (JavaScriptParserProblems.isSyntaxError(problem)) {
return true;
}
}
}
return false;
}
}
private int detectIndentationLevel(String input, int offset) {
ParserProblemReporter reporter = new ParserProblemReporter();
Script ast = createParser().parse(input, reporter);
if (ast == null || reporter.hasErrors()) {
if (DLTKCore.DEBUG)
System.out.println(reporter.getErrors());
return 0;
}
final FormatterDocument fDocument = createDocument(input);
final FormatterNodeBuilder builder = new FormatterNodeBuilder(fDocument);
IFormatterContainerNode root = builder.build(ast);
IFormatterContext context = new JavaScriptFormatterContext(0);
new JavascriptFormatterNodeRewriter(ast).rewrite(root);
FormatterIndentDetector detector = new FormatterIndentDetector(offset);
try {
root.accept(context, detector);
return detector.getLevel();
} catch (Exception e) {
// ignore all
}
return 0;
}
@Override
public int detectIndentationLevel(IDocument document, int offset) {
if (offset == 0) {
return 0;
}
return detectIndentationLevel(document.get(), offset);
}
public String format(String source, int indentationLevel)
throws FormatterException {
ParserProblemReporter reporter = new ParserProblemReporter();
Script root = createParser().parse(source, reporter);
if (root == null || reporter.hasErrors()) {
final List<IProblem> errors = reporter.getErrors();
if (!errors.isEmpty()) {
if (errors.size() == 1 && errors.get(0) instanceof JSProblem) {
final JSProblem problem = (JSProblem) errors.get(0);
throw new FormatterSyntaxProblemException(
problem.getMessage(), problem.getCause());
}
IProblem first = null;
for (IProblem problem : errors) {
if (problem.isError()
&& !(problem instanceof JSProblem)
&& problem.getSourceStart() >= 0
&& (first == null || problem.getSourceStart() < first
.getSourceStart())) {
first = problem;
}
}
if (first != null) {
throw new FormatterSyntaxProblemException(
first.getMessage());
} else {
final StringBuilder sb = new StringBuilder();
for (IProblem problem : errors) {
if (problem.isError()) {
if (sb.length() != 0) {
sb.append(", ");
}
sb.append(problem.getMessage());
}
}
throw new FormatterSyntaxProblemException(sb.toString());
}
} else {
throw new FormatterSyntaxProblemException("Syntax error");
}
}
return format(source, root, indentationLevel);
}
private String format(String source, Script ast, int indentationLevel)
throws FormatterException {
final FormatterDocument document = createDocument(source);
final FormatterNodeBuilder builder = new FormatterNodeBuilder(document);
IFormatterContainerNode root = builder.build(ast);
new JavascriptFormatterNodeRewriter(ast).rewrite(root);
IFormatterContext context = new JavaScriptFormatterContext(
indentationLevel);
JavaScriptFormatterWriter writer = new JavaScriptFormatterWriter(
document, lineDelimiter, createIndentGenerator());
writer.setWrapLength(getInt(JavaScriptFormatterConstants.WRAP_COMMENTS_LENGTH));
writer.setLinesPreserve(1);// FIXME
writer.setPreserveSpaces(false);
writer.setKeepLines(getBoolean(JavaScriptFormatterConstants.KEEP_LINES));
try {
root.accept(context, writer);
writer.flush(context);
return writer.getOutput();
} catch (Exception e) {
throw new FormatterException(e);
}
}
private FormatterDocument createDocument(String input) {
FormatterDocument document = new FormatterDocument(input);
// initialize preferences
String[] options = JavaScriptFormatterConstants.getNames();
for (int i = 0; i < options.length; i++) {
String name = options[i];
if (JavaScriptFormatterConstants.isBoolean(name)) {
document.setBoolean(name, getBoolean(name));
} else if (JavaScriptFormatterConstants.isInteger(name)) {
document.setInt(name, getInt(name));
} else if (JavaScriptFormatterConstants.isString(name)) {
document.setString(name, getString(name));
}
}
return document;
}
private JavaScriptParser createParser() {
final JavaScriptParser parser = new JavaScriptParser();
return parser;
}
}