Manifest.yml validation works!

This commit is contained in:
Kris De Volder
2016-09-23 14:43:01 -07:00
parent 9e346dd90e
commit fedf2b920c
37 changed files with 1158 additions and 121 deletions

View File

@@ -31,6 +31,11 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>util-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Java implementation of VS Code language server protocol -->
<dependency>
<groupId>io.typefox.lsapi</groupId>
@@ -74,6 +79,4 @@
<version>${jackson-2-version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -2,4 +2,6 @@ package org.springframework.ide.vscode.commons.reconcile;
public interface IDocument {
String get();
}

View File

@@ -10,8 +10,6 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.reconcile;
import org.springframework.ide.vscode.util.TextDocument;
public interface IReconcileEngine {
public void reconcile(IDocument doc, IProblemCollector problemCollector);
}

View File

@@ -14,4 +14,5 @@ package org.springframework.ide.vscode.commons.reconcile;
public interface ProblemType {
ProblemSeverity getDefaultSeverity();
String toString();
String getCode();
}

View File

@@ -11,4 +11,5 @@ public interface ReconcileProblem {
String getMessage();
int getOffset();
int getLength();
String getCode();
}

View File

@@ -0,0 +1,48 @@
package org.springframework.ide.vscode.commons.reconcile;
/**
* An implementation of {@link ReconcileProblem} that is just a simple data object.
*
* @author Kris De Volder
*/
public class ReconcileProblemImpl implements ReconcileProblem {
final private ProblemType type;
final private String msg;
final private int offset;
final private int len;
public ReconcileProblemImpl(ProblemType type, String msg, int offset, int len) {
super();
this.type = type;
this.msg = msg;
this.offset = offset;
this.len = len;
}
@Override
public ProblemType getType() {
return type;
}
@Override
public String getMessage() {
return msg;
}
@Override
public int getOffset() {
return offset;
}
@Override
public int getLength() {
return len;
}
@Override
public String getCode() {
return getType().getCode();
}
}

View File

@@ -260,7 +260,7 @@ public class SimpleTextDocumentService implements TextDocumentService {
}
public void publishDiagnostics(TextDocument doc, List<DiagnosticImpl> diagnostics) {
if (diagnostics!=null && !diagnostics.isEmpty()) {
if (diagnostics!=null) {
PublishDiagnosticsParamsImpl params = new PublishDiagnosticsParamsImpl();
params.setUri(doc.getUri());
params.setDiagnostics(diagnostics);

View File

@@ -1,7 +0,0 @@
package org.springframework.ide.vscode.util;
public class StringUtil {
public static boolean hasText(String name) {
return name!=null && !name.trim().equals("");
}
}

View File

@@ -1,12 +1,23 @@
package org.springframework.ide.vscode.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.ide.vscode.commons.reconcile.IDocument;
import io.typefox.lsapi.Position;
import io.typefox.lsapi.PositionImpl;
import io.typefox.lsapi.Range;
import io.typefox.lsapi.RangeImpl;
import io.typefox.lsapi.TextDocumentContentChangeEvent;
public class TextDocument implements IDocument {
Pattern NEWLINE = Pattern.compile("\\r|\\n|\\r\\n|\\n\\r");
private int[] _lineStarts;
private final String uri;
private String text = "";
@@ -18,23 +29,96 @@ public class TextDocument implements IDocument {
return uri;
}
public String getText() {
public String get() {
return getText();
}
public synchronized String getText() {
return text;
}
public void setText(String text) {
public synchronized void setText(String text) {
this.text = text;
this._lineStarts = null;
}
public void apply(TextDocumentContentChangeEvent change) {
Range rng = change.getRange();
if (rng==null) {
//full sync mode
this.text = change.getText();
setText(change.getText());
} else {
//incremental sync mode
throw new IllegalStateException("Incremental sync not yet implemented");
}
}
/**
* Convert a simple offset+length pair into a vscode range. This is a method on
* TextDocument because it requires splitting document into lines to determine
* line numbers from offsets.
*/
public RangeImpl toRange(int offset, int length) {
int end = offset + length;
RangeImpl range = new RangeImpl();
range.setStart(toPosition(offset));
range.setEnd(toPosition(end));
return range;
}
/**
* Determine the line-number a given offset (i.e. what line is the offset inside of?)
*/
private int lineNumber(int offset) {
int[] lineStarts = lineStarts();
// TODO Should really use binary search here for speed
int lineNumber = 0;
for (int i = 0; i < lineStarts.length; i++) {
if (lineStarts[i]<=offset) {
lineNumber = i;
} else {
return lineNumber;
}
}
return lineNumber;
}
public PositionImpl toPosition(int offset) {
int line = lineNumber(offset);
int startOfLine = startOfLine(line);
int column = offset - startOfLine;
PositionImpl pos = new PositionImpl();
pos.setCharacter(column);
pos.setLine(line);
return pos;
}
private int startOfLine(int line) {
return lineStarts()[line];
}
private synchronized int[] lineStarts() {
if (_lineStarts==null) {
_lineStarts = parseLines();
}
return _lineStarts;
}
private int[] parseLines() {
List<Integer> lineStarts = new ArrayList<>();
lineStarts.add(0);
Matcher matcher = NEWLINE.matcher(getText());
int pos = 0;
while (matcher.find(pos)) {
lineStarts.add(pos = matcher.end());
}
int[] array = new int[lineStarts.size()];
for (int i = 0; i < array.length; i++) {
array[i] = lineStarts.get(i);
}
return array;
}
}

View File

@@ -13,12 +13,14 @@
<module>language-server-commons</module>
<module>language-server-test-harness</module>
<module>yaml-commons</module>
<module>util-commons</module>
</modules>
<properties>
<junit-version>4.11</junit-version>
<assertj-version>3.5.2</assertj-version>
<slf4j-version>1.7.21</slf4j-version>
<guava-version>19.0</guava-version>
</properties>
<build>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>util-commons</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8

View File

@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@@ -0,0 +1,13 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>util-commons</artifactId>
<name>util-commons</name>
<parent>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>commons-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
</project>

View File

@@ -0,0 +1,29 @@
package org.springframework.ide.vscode.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* {@link IRequestor} that simplies stores all items received into
* a List
*
* @author Kris De Volder
*/
public class Collector<T> implements IRequestor<T> {
@SuppressWarnings("unchecked")
private List<T> nodes = Collections.EMPTY_LIST;
@Override
public void accept(T node) {
if (nodes==Collections.EMPTY_LIST) {
nodes = new ArrayList<T>();
}
nodes.add(node);
}
public List<T> get() {
return nodes;
}
}

View File

@@ -0,0 +1,68 @@
package org.springframework.ide.vscode.util;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.concurrent.CancellationException;
/**
* Utility methods to convert exceptions into other types of exceptions, status
* objects etc.
*
* @author Kris De Volder
*/
public class ExceptionUtil {
public static Throwable getDeepestCause(Throwable e) {
Throwable cause = e;
Throwable parent = e.getCause();
while (parent != null && parent != e) {
cause = parent;
parent = cause.getCause();
}
return cause;
}
public static String getMessage(Throwable e) {
// The message of nested exception is usually more interesting than the
// one on top.
Throwable cause = getDeepestCause(e);
String msg = cause.getClass().getSimpleName() + ": " + cause.getMessage();
return msg;
}
public static IllegalStateException notImplemented(String string) {
return new IllegalStateException("Not implemented: " + string);
}
public static boolean isCancelation(Throwable e) {
return (
// e instanceof OperationCanceledException ||
e instanceof InterruptedException ||
e instanceof CancellationException
// (
// e instanceof CoreException &&
// ((CoreException)e).getStatus().getSeverity()==IStatus.CANCEL
// )
);
}
public static RuntimeException unchecked(Exception e) {
return new RuntimeException(e);
}
public static String stacktrace() {
return stacktrace(new Exception("Stacktrace"));
}
public static String stacktrace(Exception exception) {
ByteArrayOutputStream dump = new ByteArrayOutputStream();
PrintStream out = new PrintStream(dump);
try {
exception.printStackTrace(out);
} finally {
out.close();
}
return dump.toString();
}
}

View File

@@ -0,0 +1,5 @@
package org.springframework.ide.vscode.util;
public interface IRequestor<T> {
void accept(T node);
}

View File

@@ -0,0 +1,24 @@
package org.springframework.ide.vscode.util;
/**
* Requestor that remembers only the last item received.
*
* @author Kris De Volder
*/
public class RememberLast<T> implements IRequestor<T> {
private T last = null;
@Override
public void accept(T node) {
this.last = node;
}
/**
* @return the last received item, may return null if no items where received.
*/
public T get() {
return last;
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.ide.vscode.util;
public class StringUtil {
public static boolean hasText(String name) {
return name!=null && !name.trim().equals("");
}
public static String collectionToDelimitedString(Iterable<String> strings, String delim) {
StringBuilder b = new StringBuilder();
boolean first = true;
for (String s : strings) {
if (!first) {
b.append(delim);
}
b.append(s);
first = false;
}
return b.toString();
}
}

View File

@@ -13,6 +13,21 @@
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>util-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>language-server-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.17</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
@@ -26,7 +41,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
<version>${guava-version}</version>
</dependency>
</dependencies>

View File

@@ -0,0 +1,190 @@
package org.springframework.ide.vscode.yaml.ast;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.SequenceNode;
/**
* A node reference represents a 'pointer' to a location where a Node is stored.
* The concept is useful because when looking for a 'path' inside an Yaml AST a
* NodeRef makes it explicit where the node is with respect to some 'container'.
* For example it allows distinguishing between a reference to Node which is
* obtained from map key versus a map value.
*
* @author Kris De Volder
*/
public abstract class NodeRef<Parent> {
public static enum Kind {
ROOT, SEQ, KEY, VAL
}
private Parent parent;
public NodeRef(Parent parent) {
this.parent = parent;
}
public Parent getParent() {
return parent;
}
public abstract Node get();
public abstract void put(Node value);
public abstract String toString();
public abstract Kind getKind();
/**
* Represents a reference to a root node, contained directly
* inside a {@link YamlFileAST}
*/
public static class RootRef extends NodeRef<YamlFileAST> {
private int index;
public RootRef(YamlFileAST file, int index) {
super(file);
this.index = index;
}
@Override
public Node get() {
return getParent().get(index);
}
@Override
public void put(Node value) {
getParent().put(index, value);
}
@Override
public String toString() {
return "ROOT["+index+"]";
}
@Override
public Kind getKind() {
return Kind.ROOT;
}
public int getIndex() {
return index;
}
}
public static class SeqRef extends NodeRef<SequenceNode> {
private int index;
public SeqRef(SequenceNode seq, int index) {
super(seq);
this.index = index;
}
@Override
public Node get() {
return getParent().getValue().get(index);
}
@Override
public void put(Node value) {
getParent().getValue().set(index, value);
}
@Override
public String toString() {
return "["+index+"]";
}
@Override
public Kind getKind() {
return Kind.SEQ;
}
public int getIndex() {
return index;
}
}
/**
* Abstract, represent reference to either a key or
* value inside a map tuple. Concrete subclasses define whether
* key or value is being accessed.
*/
public static abstract class TupleRef extends NodeRef<MappingNode> {
protected int index;
public TupleRef(MappingNode map, int index) {
super(map);
this.index = index;
}
public NodeTuple getTuple() {
return getParent().getValue().get(index);
}
public void putTuple(NodeTuple value) {
getParent().getValue().set(index, value);
}
}
/**
* References a key of a map entry
*/
public static class TupleKeyRef extends TupleRef {
public TupleKeyRef(MappingNode parent, int index) {
super(parent, index);
}
@Override
public Node get() {
return getTuple().getKeyNode();
}
@Override
public void put(Node newKey) {
NodeTuple tuple = getTuple();
putTuple(new NodeTuple(newKey, tuple.getValueNode()));
}
@Override
public String toString() {
return "@key["+index+"]";
}
@Override
public Kind getKind() {
return Kind.KEY;
}
}
/**
* References a value of a map entry
*/
public static class TupleValueRef extends TupleRef {
public TupleValueRef(MappingNode parent, int index) {
super(parent, index);
}
@Override
public Node get() {
return getTuple().getValueNode();
}
@Override
public void put(Node newValue) {
NodeTuple t = getTuple();
putTuple(new NodeTuple(t.getKeyNode(), newValue));
}
@Override
public String toString() {
String keyString = NodeUtil.asScalar(getTuple().getKeyNode());
if (keyString!=null) {
//more readable to use the key value than the index of the tuple
return "@val['"+keyString+"']";
}
return "@val["+index+"]";
}
@Override
public Kind getKind() {
return Kind.VAL;
}
public Node getKey() {
return getTuple().getKeyNode();
}
}
}

View File

@@ -0,0 +1,50 @@
package org.springframework.ide.vscode.yaml.ast;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.ScalarNode;
/**
* @author Kris De Volder
*/
public class NodeUtil {
/**
* Determines whether a node contains the given offset.
* The range a node covers between its begin and end mark is treated
* as a half open interval. The start offset is treated as included
* in the range but the end offset is excluded.
* <p>
* This is to avoid ambiguity as node ranges tend to 'join'
* together so that the end region of one node coincides with
* the start region of the next node. By treating node ranges
* as 'half open' intervals every offset is typically only
* part of two different nodes if those nodes effectively
* have overlapping ranges (i.e. only if one node contains
* the other). Thus, an operation like finding the smallest
* node that contains an offset is unambgious.
*/
public static boolean contains(Node node, int offset) {
return getStart(node)<=offset && offset<getEnd(node);
}
public static int getStart(Node node) {
return node.getStartMark().getIndex();
}
public static int getEnd(Node node) {
return node.getEndMark().getIndex();
}
/**
* Retrieve String value of a scalar node.
* @return String value or null if node is not a Scalar node.
*/
public static String asScalar(Node node) {
if (node.getNodeId()==NodeId.scalar) {
return ((ScalarNode)node).getValue();
}
return null;
}
}

View File

@@ -0,0 +1,8 @@
package org.springframework.ide.vscode.yaml.ast;
import org.springframework.ide.vscode.commons.reconcile.IDocument;
@FunctionalInterface
public interface YamlASTProvider {
YamlFileAST getAST(IDocument doc) throws Exception;
}

View File

@@ -0,0 +1,145 @@
package org.springframework.ide.vscode.yaml.ast;
import static org.springframework.ide.vscode.yaml.ast.NodeUtil.contains;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.ide.vscode.util.Collector;
import org.springframework.ide.vscode.util.IRequestor;
import org.springframework.ide.vscode.util.RememberLast;
import org.springframework.ide.vscode.yaml.ast.NodeRef.RootRef;
import org.springframework.ide.vscode.yaml.ast.NodeRef.SeqRef;
import org.springframework.ide.vscode.yaml.ast.NodeRef.TupleKeyRef;
import org.springframework.ide.vscode.yaml.ast.NodeRef.TupleValueRef;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.SequenceNode;
/**
* Represents a parsed yml file.
*
* @author Kris De Volder
*/
public class YamlFileAST {
private static final List<NodeRef<?>> NO_CHILDREN = Collections.emptyList();
private List<Node> nodes;
public YamlFileAST(Iterable<Node> iter) {
nodes = new ArrayList<Node>();
for (Node node : iter) {
nodes.add(node);
}
}
public List<NodeRef<?>> findPath(int offset) {
Collector<NodeRef<?>> path = new Collector<NodeRef<?>>();
findPath(offset, path);
return path.get();
}
/**
* Find 'smallest' ast node that contains offset. The pathRequestor will
* be called as the search progresses down the AST on all nodes on the
* path to the smallest node. If no node in the tree contains the offset
* the requestor will not be called at all.
*/
public void findPath(int offset, IRequestor<NodeRef<?>> pathRequestor) {
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
if (contains(node, offset)) {
pathRequestor.accept(new RootRef(this, i) );
findPath(node, offset, pathRequestor);
return;
}
}
}
/**
* Find smallest node that is a child of 'n' that contains 'offset'. Each visited
* node containing the offset, from the down to the found node are
* passed to the pathRequestor.
*/
private void findPath(Node n, int offset, IRequestor<NodeRef<?>> pathRequestor) {
//TODO: avoid lots of garbage production by not using 'getChildren'
// but inling getChildren (i.e a switch-case that visits
// the children without putting them into temporary collections.)
// By doing this it should be possible to avoid creaing lots of temporary
// array lists and NodeRef objects and only create NodeRef objects for
// the nodes we actually care about (i.e. the ones on the path).
List<NodeRef<?>> children = getChildren(n);
for (int i = 0; i < children.size(); i++) {
NodeRef<?> c = children.get(i);
if (contains(c.get(), offset)) {
pathRequestor.accept(c);
findPath(c.get(), offset, pathRequestor);
return;
}
}
}
public static List<NodeRef<?>> getChildren(Node n) {
switch (n.getNodeId()) {
case scalar:
return NO_CHILDREN;
case sequence:
return getChildren((SequenceNode)n);
case mapping:
return getChildren((MappingNode)n);
case anchor:
//TODO: is this right? maybe we should visit down into 'realnode'
// but do we then potentially visit the same node twice?
return NO_CHILDREN;
}
return null;
}
public List<Node> getNodes() {
return nodes;
}
private static List<NodeRef<?>> getChildren(SequenceNode seq) {
int nodes = seq.getValue().size();
ArrayList<NodeRef<?>> children = new ArrayList<NodeRef<?>>(nodes);
for (int i = 0; i < nodes; i++) {
children.add(new SeqRef(seq, i));
}
return children;
}
private static List<NodeRef<?>> getChildren(MappingNode map) {
int entries = map.getValue().size();
ArrayList<NodeRef<?>> children = new ArrayList<NodeRef<?>>(entries*2);
for (int i = 0; i < entries; i++) {
children.add(new TupleKeyRef(map, i));
children.add(new TupleValueRef(map, i));
}
return children;
}
public NodeRef<?> findNodeRef(int offset) {
RememberLast<NodeRef<?>> lastNode = new RememberLast<NodeRef<?>>();
findPath(offset, lastNode);
return lastNode.get();
}
public Node findNode(int offset) {
NodeRef<?> ref = findNodeRef(offset);
if (ref!=null) {
return ref.get();
}
return null;
}
public Node get(int index) {
return nodes.get(index);
}
public void put(int index, Node value) {
nodes.set(index, value);
}
}

View File

@@ -0,0 +1,167 @@
package org.springframework.ide.vscode.yaml.reconcile;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
import org.springframework.ide.vscode.util.ExceptionUtil;
import org.springframework.ide.vscode.util.StringUtil;
import org.springframework.ide.vscode.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.yaml.schema.YType;
import org.springframework.ide.vscode.yaml.schema.YTypeUtil;
import org.springframework.ide.vscode.yaml.schema.YTypedProperty;
import org.springframework.ide.vscode.yaml.schema.YamlSchema;
import org.springframework.ide.vscode.yaml.util.ValueParser;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
private final IProblemCollector problems;
private final YamlSchema schema;
private final YTypeUtil typeUtil;
public SchemaBasedYamlASTReconciler(IProblemCollector problems, YamlSchema schema) {
this.problems = problems;
this.schema = schema;
this.typeUtil = schema.getTypeUtil();
}
@Override
public void reconcile(YamlFileAST ast) {
List<Node> nodes = ast.getNodes();
if (nodes!=null && !nodes.isEmpty()) {
for (Node node : nodes) {
reconcile(node, schema.getTopLevelType());
}
}
}
private void reconcile(Node node, YType type) {
if (type!=null) {
switch (node.getNodeId()) {
case mapping:
MappingNode map = (MappingNode) node;
if (typeUtil.isMap(type)) {
for (NodeTuple entry : map.getValue()) {
reconcile(entry.getKeyNode(), typeUtil.getKeyType(type));
reconcile(entry.getValueNode(), typeUtil.getDomainType(type));
}
} else if (typeUtil.isBean(type)) {
Map<String, YTypedProperty> beanProperties = typeUtil.getPropertiesMap(type);
for (NodeTuple entry : map.getValue()) {
Node keyNode = entry.getKeyNode();
String key = NodeUtil.asScalar(keyNode);
if (key==null) {
expectScalar(node);
} else {
YTypedProperty prop = beanProperties.get(key);
if (prop==null) {
unknownBeanProperty(keyNode, type, key);
} else {
reconcile(entry.getValueNode(), prop.getType());
}
}
}
} else {
expectTypeButFoundMap(type, node);
}
break;
case sequence:
SequenceNode seq = (SequenceNode) node;
if (typeUtil.isSequencable(type)) {
for (Node el : seq.getValue()) {
reconcile(el, typeUtil.getDomainType(type));
}
} else {
expectTypeButFoundSequence(type, node);
}
break;
case scalar:
if (typeUtil.isAtomic(type)) {
ValueParser parser = typeUtil.getValueParser(type);
if (parser!=null) {
try {
parser.parse(NodeUtil.asScalar(node));
} catch (Exception e) {
String msg = ExceptionUtil.getMessage(e);
valueParseError(type, node, msg);
}
}
} else {
expectTypeButFoundScalar(type, node);
}
break;
default:
// other stuff we don't check
}
}
}
private void valueParseError(YType type, Node node, String parseErrorMsg) {
String msg= "Couldn't parse as '"+describe(type)+"'";
if (StringUtil.hasText(parseErrorMsg)) {
msg += " ("+parseErrorMsg+")";
}
problem(node, msg);
}
private void unknownBeanProperty(Node keyNode, YType type, String name) {
problem(keyNode, "Unknown property '"+name+"' for type '"+typeUtil.niceTypeName(type)+"'");
}
private void expectScalar(Node node) {
problem(node, "Expecting a 'Scalar' node but got "+describe(node));
}
private String describe(Node node) {
switch (node.getNodeId()) {
case scalar:
return "'"+((ScalarNode)node).getValue()+"'";
case mapping:
return "a 'Mapping' node";
case sequence:
return "a 'Sequence' node";
case anchor:
return "a 'Anchor' node";
default:
throw new IllegalStateException("Missing switch case");
}
}
private void expectTypeButFoundScalar(YType type, Node node) {
problem(node, "Expecting a '"+describe(type)+"' but found a 'Scalar'");
}
private void expectTypeButFoundSequence(YType type, Node node) {
problem(node, "Expecting a '"+describe(type)+"' but found a 'Sequence'");
}
private void expectTypeButFoundMap(YType type, Node node) {
problem(node, "Expecting a '"+describe(type)+"' but found a 'Map'");
}
private String describe(YType type) {
if (typeUtil.isAtomic(type)) {
return typeUtil.niceTypeName(type);
}
ArrayList<String> expectedNodeTypes = new ArrayList<>();
if (typeUtil.isBean(type) || typeUtil.isMap(type)) {
expectedNodeTypes.add("Map");
}
if (typeUtil.isSequencable(type)) {
expectedNodeTypes.add("Sequence");
}
return StringUtil.collectionToDelimitedString(expectedNodeTypes, " or ");
}
private void problem(Node node, String msg) {
problems.accept(YamlSchemaProblems.schemaProblem(msg, node));
}
}

View File

@@ -0,0 +1,7 @@
package org.springframework.ide.vscode.yaml.reconcile;
import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
public interface YamlASTReconciler {
void reconcile(YamlFileAST ast);
}

View File

@@ -0,0 +1,54 @@
package org.springframework.ide.vscode.yaml.reconcile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.reconcile.IDocument;
import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.parser.ParserException;
import org.yaml.snakeyaml.scanner.ScannerException;
/**
* @author Kris De Volder
*/
public abstract class YamlReconcileEngine implements IReconcileEngine {
final static Logger logger = LoggerFactory.getLogger(YamlReconcileEngine.class);
protected final YamlASTProvider parser;
public YamlReconcileEngine(YamlASTProvider parser) {
this.parser = parser;
}
@Override
public void reconcile(IDocument doc, IProblemCollector problemCollector) {
problemCollector.beginCollecting();
try {
YamlFileAST ast = parser.getAST(doc);
YamlASTReconciler reconciler = getASTReconciler(doc, problemCollector);
if (reconciler!=null) {
reconciler.reconcile(ast);
}
} catch (ParserException e) {
String msg = e.getProblem();
Mark mark = e.getProblemMark();
problemCollector.accept(syntaxError(msg, mark.getIndex(), 1));
} catch (ScannerException e) {
String msg = e.getProblem();
Mark mark = e.getProblemMark();
problemCollector.accept(syntaxError(msg, mark.getIndex(), 1));
} catch (Exception e) {
logger.error("unexpected error during reconcile", e);
} finally {
problemCollector.endCollecting();
}
}
protected abstract ReconcileProblem syntaxError(String msg, int offset, int length);
protected abstract YamlASTReconciler getASTReconciler(IDocument doc, IProblemCollector problemCollector);
}

View File

@@ -0,0 +1,29 @@
package org.springframework.ide.vscode.yaml.reconcile;
import org.springframework.ide.vscode.commons.reconcile.IDocument;
import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.yaml.schema.YamlSchema;
/**
* @author Kris De Volder
*/
public final class YamlSchemaBasedReconcileEngine extends YamlReconcileEngine {
private final YamlSchema schema;
public YamlSchemaBasedReconcileEngine(YamlASTProvider parser, YamlSchema schema) {
super(parser);
this.schema = schema;
}
@Override
protected ReconcileProblem syntaxError(String msg, int offset, int length) {
return YamlSchemaProblems.syntaxProblem(msg, offset, length);
}
@Override
protected YamlASTReconciler getASTReconciler(IDocument doc, IProblemCollector problems) {
return new SchemaBasedYamlASTReconciler(problems, schema);
}
}

View File

@@ -0,0 +1,45 @@
package org.springframework.ide.vscode.yaml.reconcile;
import org.springframework.ide.vscode.commons.reconcile.ProblemSeverity;
import org.springframework.ide.vscode.commons.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.reconcile.ReconcileProblemImpl;
import org.yaml.snakeyaml.nodes.Node;
/**
* Methods for creating reconciler problems for Schema based reconciler implementation.
*
* @author Kris De Volder
*/
public class YamlSchemaProblems {
private static final ProblemType SCHEMA_PROBLEM = problemType("YamlSchemaProblem");
private static final ProblemType SYNTAX_PROBLEM = problemType("YamlSyntaxProblem");
private static ProblemType problemType(final String typeName) {
return new ProblemType() {
@Override
public String toString() {
return typeName;
}
@Override
public ProblemSeverity getDefaultSeverity() {
return ProblemSeverity.ERROR;
}
@Override
public String getCode() {
return typeName;
}
};
}
public static ReconcileProblem syntaxProblem(String msg, int offset, int len) {
return new ReconcileProblemImpl(SYNTAX_PROBLEM, msg, offset, len);
}
public static ReconcileProblem schemaProblem(String msg, Node node) {
int start = node.getStartMark().getIndex();
int end = node.getEndMark().getIndex();
return new ReconcileProblemImpl(SCHEMA_PROBLEM, msg, start, end-start);
}
}

View File

@@ -42,6 +42,7 @@ function getClasspath(context: VSCode.ExtensionContext):string {
/** Called when extension is activated */
export function activate(context: VSCode.ExtensionContext) {
VSCode.window.showInformationMessage("Activating manifet.yml extension");
let javaExecutablePath = findJavaExecutable('java');
if (javaExecutablePath == null) {
@@ -103,7 +104,7 @@ export function activate(context: VSCode.ExtensionContext) {
let args = [
'-Dserver.port=' + port,
'-cp', classpath,
'org.springframework.ide.vscode.yaml.Main'
'org.springframework.ide.vscode.cloudfoundry.manifest.editor.Main'
];
if (DEBUG) {
args.unshift(DEBUG_ARG);

View File

@@ -1,55 +0,0 @@
package org.springframework.ide.vscode.cloudfoundry.manifest.editor;
///*******************************************************************************
// * Copyright (c) 2015 Pivotal, Inc.
// * All rights reserved. This program and the accompanying materials
// * are made available under the terms of the Eclipse Public License v1.0
// * which accompanies this distribution, and is available at
// * http://www.eclipse.org/legal/epl-v10.html
// *
// * Contributors:
// * Pivotal, Inc. - initial API and implementation
// *******************************************************************************/
//package org.springframework.ide.eclipse.cloudfoundry.manifest.editor;
//
//import org.dadacoalition.yedit.editor.YEditSourceViewerConfiguration;
//import org.springframework.ide.eclipse.editor.support.util.ShellProviders;
//import org.springframework.ide.eclipse.editor.support.yaml.AbstractYamlEditor;
//
//
//public class ManifestYamlEditor extends AbstractYamlEditor {
//
// @Override
// protected YEditSourceViewerConfiguration createSourceViewerConfiguration() {
// return new ManifestYamlSourceViewerConfiguration(ShellProviders.from(this));
// }
//
// @Override
// protected void initializeEditor() {
// super.initializeEditor();
//// SpringPropertiesEditorPlugin.getIndexManager().addListener(this);
//// SpringPropertiesEditorPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
// }
//
//// @Override
//// public void changed(SpringPropertiesIndexManager info) {
//// if (sourceViewerConf!=null) {
//// sourceViewerConf.forceReconcile();
//// }
//// }
//
// @Override
// public void dispose() {
// super.dispose();
//// SpringPropertiesEditorPlugin.getIndexManager().removeListener(this);
//// SpringPropertiesEditorPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
// }
//
//// @Override
//// public void propertyChange(PropertyChangeEvent event) {
//// if (event.getProperty().startsWith(ProblemSeverityPreferencesUtil.PREFERENCE_PREFIX)) {
//// if (sourceViewerConf!=null) {
//// sourceViewerConf.forceReconcile();
//// }
//// }
//// }
//}

View File

@@ -2,15 +2,27 @@ package org.springframework.ide.vscode.cloudfoundry.manifest.editor;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.inject.Provider;
import org.springframework.ide.vscode.commons.reconcile.IDocument;
import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.util.Futures;
import org.springframework.ide.vscode.util.SimpleLanguageServer;
import org.springframework.ide.vscode.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.util.TextDocument;
import org.springframework.ide.vscode.yaml.ErrorCodes;
import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.yaml.reconcile.SchemaBasedYamlASTReconciler;
import org.springframework.ide.vscode.yaml.reconcile.YamlASTReconciler;
import org.springframework.ide.vscode.yaml.reconcile.YamlSchemaBasedReconcileEngine;
import org.springframework.ide.vscode.yaml.schema.YValueHint;
import org.springframework.ide.vscode.yaml.schema.YamlSchema;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.MarkedYAMLException;
import org.yaml.snakeyaml.error.YAMLException;
@@ -32,10 +44,13 @@ import io.typefox.lsapi.ServerCapabilitiesImpl;
public class ManifestYamlLanguageServer extends SimpleLanguageServer {
private static final Provider<Collection<YValueHint>> NO_BUILDPACKS = () -> ImmutableList.of();
private Yaml yaml = new Yaml();
public ManifestYamlLanguageServer() {
SimpleTextDocumentService documents = getTextDocumentService();
// SimpleWorkspaceService workspace = getWorkspaceService();
documents.onDidChangeContent(params -> {
TextDocument doc = params.getDocument();
@@ -104,55 +119,36 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
}
private void validateDocument(SimpleTextDocumentService documents, TextDocument doc) {
List<DiagnosticImpl> diagnostics = reconcile(documents, doc);
documents.publishDiagnostics(doc, diagnostics);
IProblemCollector problems = new IProblemCollector() {
private List<DiagnosticImpl> diagnostics = new ArrayList<>();
@Override
public void endCollecting() {
documents.publishDiagnostics(doc, diagnostics);
}
@Override
public void beginCollecting() {
diagnostics.clear();
}
@Override
public void accept(ReconcileProblem problem) {
DiagnosticImpl d = new DiagnosticImpl();
d.setCode(problem.getCode());
d.setMessage(problem.getMessage());
d.setRange(doc.toRange(problem.getOffset(), problem.getLength()));
diagnostics.add(d);
}
};
YamlSchema schema = new ManifestYmlSchema(NO_BUILDPACKS);
YamlASTProvider parser = new YamlParser(yaml);
YamlSchemaBasedReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema);
engine.reconcile(doc, problems);
}
protected List<DiagnosticImpl> reconcile(SimpleTextDocumentService documents, TextDocument doc) {
try {
Iterator<Node> asts = yaml.composeAll(new StringReader(doc.getText())).iterator();
while (asts.hasNext()) {
asts.next();
}
return ImmutableList.of();
} catch (YAMLException e) {
return ImmutableList.of(parseError(e));
}
}
private DiagnosticImpl parseError(YAMLException e) {
DiagnosticImpl d = new DiagnosticImpl();
d.setMessage(getMessage(e));
d.setRange(getRange(e));
d.setSeverity(Diagnostic.SEVERITY_ERROR);
d.setCode(ErrorCodes.YAML_SYNTAX_ERROR);
d.setSource("yaml");
return d;
}
private String getMessage(YAMLException e) {
if (e instanceof MarkedYAMLException) {
return ((MarkedYAMLException) e).getProblem();
}
return e.getMessage();
}
private RangeImpl getRange(YAMLException _e) {
if (_e instanceof MarkedYAMLException) {
MarkedYAMLException e = (MarkedYAMLException) _e;
PositionImpl start = new PositionImpl();
start.setLine(e.getProblemMark().getLine());
start.setCharacter(e.getProblemMark().getColumn());
RangeImpl rng = new RangeImpl();
rng.setStart(start);
rng.setEnd(start);
return rng;
}
return null;
}
@Override
protected ServerCapabilitiesImpl getServerCapabilities() {
ServerCapabilitiesImpl c = new ServerCapabilitiesImpl();

View File

@@ -0,0 +1,23 @@
package org.springframework.ide.vscode.cloudfoundry.manifest.editor;
import java.io.StringReader;
import org.springframework.ide.vscode.commons.reconcile.IDocument;
import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
import org.yaml.snakeyaml.Yaml;
public class YamlParser implements YamlASTProvider {
private Yaml yaml;
public YamlParser(Yaml yaml) {
this.yaml = yaml;
}
@Override
public YamlFileAST getAST(IDocument doc) throws Exception {
return new YamlFileAST(yaml.composeAll(new StringReader(doc.get())));
}
}

View File

@@ -1,2 +1,5 @@
foo: bar
- asss
applications:
- name: foo
foo: bar
builpack: java