Integrate properties parser into boot properties extension
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
package org.springframework.ide.vscode.application.yaml;
|
||||
package org.springframework.ide.vscode.application.properties.metadata;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertiesIndexManager;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
@@ -19,6 +19,7 @@ import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepos
|
||||
import org.springframework.boot.configurationmetadata.ConfigurationMetadataSource;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
|
||||
|
||||
@@ -125,4 +126,31 @@ public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
|
||||
protected String getKey(PropertyInfo entry) {
|
||||
return entry.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the longest known property that is a prefix of the given name. Here prefix does not mean
|
||||
* 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So
|
||||
* 'prefix' is not allowed to end in the middle of a 'segment'.
|
||||
*/
|
||||
public static PropertyInfo findLongestValidProperty(FuzzyMap<PropertyInfo> index, String name) {
|
||||
int bracketPos = name.indexOf('[');
|
||||
int endPos = bracketPos>=0?bracketPos:name.length();
|
||||
PropertyInfo prop = null;
|
||||
String prefix = null;
|
||||
while (endPos>0 && prop==null) {
|
||||
prefix = name.substring(0, endPos);
|
||||
String canonicalPrefix = StringUtil.camelCaseToHyphens(prefix);
|
||||
prop = index.get(canonicalPrefix);
|
||||
if (prop==null) {
|
||||
endPos = name.lastIndexOf('.', endPos-1);
|
||||
}
|
||||
}
|
||||
if (prop!=null) {
|
||||
//We should meet caller's expectation that matched properties returned by this method
|
||||
// match the names exactly even if we found them using relaxed name matching.
|
||||
return prop.withId(prefix);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package org.springframework.ide.vscode.commons.languageserver.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.util.Assert;
|
||||
|
||||
/**
|
||||
* A non-sucky alternative to {@link IRegion}. Represents a region of text in a document.
|
||||
* <p>
|
||||
* Caution: assumes the underlying document is not mutated during the lifetime of the
|
||||
* region object (otherwise start/end positions may no longer be valid).
|
||||
* <p>
|
||||
* Implements {@link CharSequence} for convenience (e.g you can use {@link DocumentRegion} as
|
||||
* input to a {@link Pattern} and other standard JRE functions which expect a {@link CharSequence}.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class DocumentRegion implements CharSequence {
|
||||
final IDocument doc;
|
||||
final int start;
|
||||
final int end;
|
||||
public DocumentRegion(IDocument doc, IRegion r) {
|
||||
this(doc,
|
||||
r.getOffset(),
|
||||
r.getOffset()+r.getLength()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link DocumentRegion} on a given document. Tries its
|
||||
* best to behave sensibly when passed 'strange' coordinates by
|
||||
* adjusting them logically rather than throw an Exception.
|
||||
* <p>
|
||||
* A position before the start of the document is moved to be the start
|
||||
* of the document.
|
||||
* <p>
|
||||
* A position after the end of the document is moved to the end
|
||||
* of the document.
|
||||
* <p>
|
||||
* If 'end' position is before the start position it is moved be
|
||||
* exactly at the start position (this avoids region with
|
||||
* negative length).
|
||||
*/
|
||||
public DocumentRegion(IDocument doc, int start, int end) {
|
||||
this.doc = doc;
|
||||
this.start = limitRange(start, 0, doc.getLength());
|
||||
this.end = limitRange(end, start, doc.getLength());
|
||||
}
|
||||
|
||||
private int limitRange(int offset, int min, int max) {
|
||||
if (offset<min) {
|
||||
return min;
|
||||
}
|
||||
if (offset>max) {
|
||||
return max;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DocumentUtil.textBetween(doc, start, end);
|
||||
}
|
||||
|
||||
public DocumentRegion trim() {
|
||||
return trimEnd().trimStart();
|
||||
}
|
||||
|
||||
public DocumentRegion trimStart() {
|
||||
int howMany = 0;
|
||||
int len = length();
|
||||
while (howMany<len && Character.isWhitespace(charAt(howMany))) {
|
||||
howMany++;
|
||||
}
|
||||
return subSequence(howMany, len);
|
||||
}
|
||||
|
||||
public DocumentRegion trimEnd() {
|
||||
int howMany = 0; //how many chars to remove from the end
|
||||
int len = length();
|
||||
int lastChar = len-1;
|
||||
while (howMany<len && Character.isWhitespace(charAt(lastChar-howMany))) {
|
||||
howMany++;
|
||||
}
|
||||
if (howMany>0) {
|
||||
return subSequence(0, len-howMany);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets character from the region, offset from the start of the region
|
||||
* @return the character from the document (char)0 if the offset is outside the region.
|
||||
*/
|
||||
@Override
|
||||
public char charAt(int offset) {
|
||||
if (offset<0 || offset>=length()) {
|
||||
throw new IndexOutOfBoundsException(""+offset);
|
||||
}
|
||||
try {
|
||||
return doc.getChar(start+offset);
|
||||
} catch (BadLocationException e) {
|
||||
throw new IndexOutOfBoundsException(""+offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentRegion subSequence(int start, int end) {
|
||||
int len = length();
|
||||
Assert.isLegal(start>=0);
|
||||
Assert.isLegal(end<=len);
|
||||
if (start==0 && end==len) {
|
||||
return this;
|
||||
}
|
||||
return new DocumentRegion(doc, this.start+start, this.start+end);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return length()==0;
|
||||
}
|
||||
|
||||
public DocumentRegion subSequence(int start) {
|
||||
return subSequence(start, length());
|
||||
}
|
||||
|
||||
public IRegion asRegion() {
|
||||
return new Region(start, end-start);
|
||||
}
|
||||
|
||||
public int indexOf(char ch, int fromIndex) {
|
||||
while (fromIndex < length()) {
|
||||
if (charAt(fromIndex)==ch) {
|
||||
return fromIndex;
|
||||
}
|
||||
fromIndex++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public DocumentRegion[] split(char c) {
|
||||
List<DocumentRegion> pieces = new ArrayList<>();
|
||||
int start = 0;
|
||||
int end;
|
||||
while ((end=indexOf(c, start))>=0) {
|
||||
pieces.add(subSequence(start, end));
|
||||
start = end+1;
|
||||
}
|
||||
// Do not forget the last piece!
|
||||
pieces.add(subSequence(start, length()));
|
||||
return pieces.toArray(new DocumentRegion[pieces.size()]);
|
||||
}
|
||||
|
||||
public DocumentRegion[] split(Pattern delimiter) {
|
||||
List<DocumentRegion> pieces = new ArrayList<>();
|
||||
int start = 0;
|
||||
Matcher matcher = delimiter.matcher(this);
|
||||
while (matcher.find(start)) {
|
||||
int end = matcher.start();
|
||||
pieces.add(subSequence(start, end));
|
||||
start = matcher.end();
|
||||
}
|
||||
// Do not forget the last piece!
|
||||
pieces.add(subSequence(start, length()));
|
||||
return pieces.toArray(new DocumentRegion[pieces.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single occurrence of pat from the start of this region.
|
||||
*/
|
||||
public DocumentRegion trimStart(Pattern pat) {
|
||||
pat = Pattern.compile("^("+pat.pattern()+")");
|
||||
Matcher matcher = pat.matcher(this);
|
||||
if (matcher.find()) {
|
||||
return subSequence(matcher.end());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single occurrence of pat from the end of this region.
|
||||
*/
|
||||
public DocumentRegion trimEnd(Pattern pat) {
|
||||
pat = Pattern.compile("("+pat.pattern()+")$");
|
||||
Matcher matcher = pat.matcher(this);
|
||||
if (matcher.find()) {
|
||||
return subSequence(0, matcher.start());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region after this one with a given lenght.
|
||||
* <p>
|
||||
* If the document is too short to provide the requested lenght
|
||||
* then the region is truncated to end of the document.
|
||||
*/
|
||||
public DocumentRegion textAfter(int len) {
|
||||
Assert.isLegal(len>=0);
|
||||
return new DocumentRegion(doc, end, end+len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region before this one with a given lenght.
|
||||
* <p>
|
||||
* If the requested region extends before the start of the document,
|
||||
* then the region is shortened so its start coincides with document start.
|
||||
*/
|
||||
public DocumentRegion textBefore(int len) {
|
||||
Assert.isLegal(len>=0);
|
||||
return new DocumentRegion(doc, start-len, start);
|
||||
}
|
||||
|
||||
public IDocument getDocument() {
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start of this region in 'absolute' terms (i.e. relative to the document).
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end of this region in 'absolute' terms (i.e. relative to the document).
|
||||
*/
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given document offset into an offset relative to this region.
|
||||
*/
|
||||
public int toRelative(int offset) {
|
||||
return offset-start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return getEnd() - getStart();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.springframework.ide.vscode.java.properties.parser.Parser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.Problem;
|
||||
import org.springframework.ide.vscode.java.properties.parser.ProblemCodes;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesFileEscapes;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
@@ -230,7 +231,12 @@ public class AntlrParser implements Parser {
|
||||
|
||||
@Override
|
||||
public String decode() {
|
||||
return context.getText().replace("\\:", ":").replace("\\=", "=");
|
||||
// return context.getText().replace("\\:", ":").replace("\\=", "=");
|
||||
try {
|
||||
return PropertiesFileEscapes.unescape(context.getText());
|
||||
} catch (Exception e) {
|
||||
return context.getText().replace("\\:", ":").replace("\\=", "=");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -249,8 +255,13 @@ public class AntlrParser implements Parser {
|
||||
private void init() {
|
||||
// Remove the separator, if it exists
|
||||
value = context.getText().replaceAll("^\\s*[:=]?\\s*", "");
|
||||
// Remove all escaped line breaks with trailing spaces
|
||||
// Remove all escaped line breaks with trailing spaces
|
||||
decoded = value.replaceAll("\\\\(\r?\n|\r)[ \t\f]*", "");
|
||||
try {
|
||||
decoded = PropertiesFileEscapes.unescape(decoded);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
package org.springframework.ide.vscode.java.properties.parser;
|
||||
|
||||
/**
|
||||
* Helper class to convert between Java chars and the escaped form that must be used in .properties
|
||||
* files.
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public class PropertiesFileEscapes {
|
||||
|
||||
private static final char[] HEX_DIGITS= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
private static char toHex(int halfByte) {
|
||||
return HEX_DIGITS[(halfByte & 0xF)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decimal value of the Hex digit, or -1 if the digit is not a valid Hex digit.
|
||||
*
|
||||
* @param digit the Hex digit
|
||||
* @return the decimal value of digit, or -1 if digit is not a valid Hex digit.
|
||||
*/
|
||||
private static int getHexDigitValue(char digit) {
|
||||
switch (digit) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return digit - '0';
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
return 10 + digit - 'a';
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
return 10 + digit - 'A';
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Java char to the escaped form that must be used in .properties files.
|
||||
*
|
||||
* @param c the Java char
|
||||
* @return escaped string
|
||||
*/
|
||||
public static String escape(char c) {
|
||||
return escape(c, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert characters in a Java string to the escaped form that must be used in .properties
|
||||
* files.
|
||||
*
|
||||
* @param s the Java string
|
||||
* @param escapeWhitespaceChars if <code>true</code>, escape whitespace characters
|
||||
* @param escapeBackslash if <code>true</code>, escape backslash characters
|
||||
* @param escapeUnicodeChars if <code>true</code>, escape unicode characters
|
||||
* @return escaped string
|
||||
*/
|
||||
public static String escape(String s, boolean escapeWhitespaceChars, boolean escapeBackslash, boolean escapeUnicodeChars) {
|
||||
StringBuffer sb= new StringBuffer(s.length());
|
||||
int length= s.length();
|
||||
for (int i= 0; i < length; i++) {
|
||||
char c= s.charAt(i);
|
||||
sb.append(escape(c, escapeWhitespaceChars, escapeBackslash, escapeUnicodeChars));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Java char to the escaped form that must be used in .properties files.
|
||||
*
|
||||
* @param c the Java char
|
||||
* @param escapeWhitespaceChars if <code>true</code>, escape whitespace characters
|
||||
* @param escapeBackslash if <code>true</code>, escape backslash characters
|
||||
* @param escapeUnicodeChars if <code>true</code>, escape unicode characters
|
||||
* @return escaped string
|
||||
*/
|
||||
public static String escape(char c, boolean escapeWhitespaceChars, boolean escapeBackslash, boolean escapeUnicodeChars) {
|
||||
switch (c) {
|
||||
case '\t':
|
||||
return escapeWhitespaceChars ? "\\t" : "\t"; //$NON-NLS-1$//$NON-NLS-2$
|
||||
case '\n':
|
||||
return escapeWhitespaceChars ? "\\n" : "\n"; //$NON-NLS-1$//$NON-NLS-2$
|
||||
case '\f':
|
||||
return escapeWhitespaceChars ? "\\f" : "\r"; //$NON-NLS-1$//$NON-NLS-2$
|
||||
case '\r':
|
||||
return escapeWhitespaceChars ? "\\r" : "\r"; //$NON-NLS-1$//$NON-NLS-2$
|
||||
case '\\':
|
||||
return escapeBackslash ? "\\\\" : "\\"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
default:
|
||||
if (escapeUnicodeChars && ((c < 0x0020) || (c > 0x007e && c <= 0x00a0) || (c > 0x00ff))) {
|
||||
//NBSP (0x00a0) is escaped to differentiate from normal space character
|
||||
return new StringBuffer()
|
||||
.append('\\')
|
||||
.append('u')
|
||||
.append(toHex((c >> 12) & 0xF))
|
||||
.append(toHex((c >> 8) & 0xF))
|
||||
.append(toHex((c >> 4) & 0xF))
|
||||
.append(toHex(c & 0xF)).toString();
|
||||
|
||||
} else
|
||||
return String.valueOf(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an escaped string to a string composed of Java characters.
|
||||
*
|
||||
* @param s the escaped string
|
||||
* @return string composed of Java characters
|
||||
* @throws CoreException if the escaped string has a malformed \\uxxx sequence
|
||||
*/
|
||||
public static String unescape(String s) throws Exception {
|
||||
boolean isValidEscapedString= true;
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
char aChar;
|
||||
int len= s.length();
|
||||
StringBuffer outBuffer= new StringBuffer(len);
|
||||
|
||||
for (int x= 0; x < len;) {
|
||||
aChar= s.charAt(x++);
|
||||
if (aChar == '\\') {
|
||||
if (x > len - 1) {
|
||||
return outBuffer.toString(); // silently ignore the \
|
||||
}
|
||||
aChar= s.charAt(x++);
|
||||
if (aChar == 'u') {
|
||||
// Read the xxxx
|
||||
int value= 0;
|
||||
if (x > len - 4) {
|
||||
throw new Exception("Malformed encoding for properties file");
|
||||
}
|
||||
StringBuffer buf= new StringBuffer("\\u"); //$NON-NLS-1$
|
||||
int digit= 0;
|
||||
for (int i= 0; i < 4; i++) {
|
||||
aChar= s.charAt(x++);
|
||||
digit= getHexDigitValue(aChar);
|
||||
if (digit == -1) {
|
||||
isValidEscapedString= false;
|
||||
x--;
|
||||
break;
|
||||
}
|
||||
value= (value << 4) + digit;
|
||||
buf.append(aChar);
|
||||
}
|
||||
outBuffer.append(digit == -1 ? buf.toString() : String.valueOf((char)value));
|
||||
} else if (aChar == 't') {
|
||||
outBuffer.append('\t');
|
||||
} else if (aChar == 'n') {
|
||||
outBuffer.append('\n');
|
||||
} else if (aChar == 'f') {
|
||||
outBuffer.append('\f');
|
||||
} else if (aChar == 'r') {
|
||||
outBuffer.append('\r');
|
||||
} else {
|
||||
outBuffer.append(aChar); // silently ignore the \
|
||||
}
|
||||
} else
|
||||
outBuffer.append(aChar);
|
||||
}
|
||||
if (isValidEscapedString) {
|
||||
return outBuffer.toString();
|
||||
} else {
|
||||
throw new Exception("Malformed encoding for properties file");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape backslash characters in a string.
|
||||
*
|
||||
* @param s the escaped string
|
||||
* @return string with backslash characters unescaped
|
||||
*/
|
||||
public static String unescapeBackslashes(String s) {
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
char c;
|
||||
int length= s.length();
|
||||
StringBuffer outBuffer= new StringBuffer(length);
|
||||
|
||||
for (int i= 0; i < length;) {
|
||||
c= s.charAt(i++);
|
||||
if (c == '\\') {
|
||||
c= s.charAt(i++);
|
||||
}
|
||||
outBuffer.append(c);
|
||||
}
|
||||
|
||||
return outBuffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given text contains any invalid escape sequence.
|
||||
*
|
||||
* @param text the text
|
||||
* @return <code>true</code> if text contains an invalid escape sequence, <code>false</code>
|
||||
* otherwise
|
||||
*/
|
||||
public static boolean containsInvalidEscapeSequence(String text) {
|
||||
try {
|
||||
//check for invalid unicode escapes
|
||||
unescape(text);
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int length= text.length();
|
||||
for (int i= 0; i < length; i++) {
|
||||
char c= text.charAt(i);
|
||||
if (c == '\\') {
|
||||
if (i < length - 1) {
|
||||
char nextC= text.charAt(i + 1);
|
||||
switch (nextC) {
|
||||
case 't':
|
||||
case 'n':
|
||||
case 'f':
|
||||
case 'r':
|
||||
case 'u':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '=':
|
||||
case ':':
|
||||
break;
|
||||
case '\\':
|
||||
i++;
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given text contains an unescaped backslash character.
|
||||
*
|
||||
* @param text the text
|
||||
* @return <code>true</code> if text contains an unescaped backslash character,
|
||||
* <code>false</code> otherwise
|
||||
*/
|
||||
public static boolean containsUnescapedBackslash(String text) {
|
||||
int length= text.length();
|
||||
for (int i= 0; i < length; i++) {
|
||||
char c= text.charAt(i);
|
||||
if (c == '\\') {
|
||||
if (i < length - 1) {
|
||||
char nextC= text.charAt(i + 1);
|
||||
switch (nextC) {
|
||||
case '\\':
|
||||
i++;
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given text contains only escaped backslash characters and no unescaped backslash
|
||||
* character.
|
||||
*
|
||||
* @param text the text
|
||||
* @return <code>true</code> if text contains only escaped backslash characters,
|
||||
* <code>false</code> otherwise
|
||||
*/
|
||||
public static boolean containsEscapedBackslashes(String text) {
|
||||
boolean result= false;
|
||||
int length= text.length();
|
||||
for (int i= 0; i < length; i++) {
|
||||
char c= text.charAt(i);
|
||||
if (c == '\\') {
|
||||
if (i < length - 1) {
|
||||
char nextC= text.charAt(i + 1);
|
||||
switch (nextC) {
|
||||
case '\\':
|
||||
i++;
|
||||
result= true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.springframework.ide.vscode.properties.editor.test.harness;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.application.properties;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.application.properties.reconcile.SpringPropertiesReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.BadWordReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.TextDocument;
|
||||
@@ -19,11 +22,7 @@ import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
|
||||
import org.springframework.ide.vscode.java.properties.parser.Parser;
|
||||
|
||||
import io.typefox.lsapi.Diagnostic;
|
||||
import io.typefox.lsapi.DiagnosticSeverity;
|
||||
import io.typefox.lsapi.ServerCapabilities;
|
||||
import io.typefox.lsapi.TextDocumentSyncKind;
|
||||
import io.typefox.lsapi.impl.DiagnosticImpl;
|
||||
import io.typefox.lsapi.impl.ServerCapabilitiesImpl;
|
||||
|
||||
/**
|
||||
@@ -40,41 +39,53 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer {
|
||||
|
||||
private ParseResults parseResults;
|
||||
private Parser parser;
|
||||
|
||||
private SpringPropertyIndexProvider indexProvider;
|
||||
private TypeUtilProvider typeUtilProvider;
|
||||
|
||||
public ApplicationPropertiesLanguageServer() {
|
||||
|
||||
public ApplicationPropertiesLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) {
|
||||
this.indexProvider = indexProvider;
|
||||
this.typeUtilProvider = typeUtilProvider;
|
||||
this.parser = new AntlrParser();
|
||||
SimpleTextDocumentService documents = getTextDocumentService();
|
||||
|
||||
|
||||
IReconcileEngine reconcileEngine = getReconcileEngine();
|
||||
documents.onDidChangeContent(params -> {
|
||||
System.out.println("Document changed: "+params);
|
||||
TextDocument doc = params.getDocument();
|
||||
parseResults = parser.parse(doc.getText());
|
||||
validateDocument(documents, doc);
|
||||
validateWith(doc, reconcileEngine);
|
||||
});
|
||||
|
||||
// documents.onDidChangeContent(params -> {
|
||||
// System.out.println("Document changed: "+params);
|
||||
// TextDocument doc = params.getDocument();
|
||||
// parseResults = parser.parse(doc.getText());
|
||||
// validateDocument(documents, doc);
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
private void validateDocument(SimpleTextDocumentService documents, TextDocument doc) {
|
||||
documents.publishDiagnostics(doc, parseResults.syntaxErrors.stream().map(problem -> {
|
||||
DiagnosticImpl diagnostic = new DiagnosticImpl();
|
||||
diagnostic.setMessage(createSyntaxErrorMessage(problem.getMessage()));
|
||||
diagnostic.setCode(problem.getCode());
|
||||
diagnostic.setSeverity(DiagnosticSeverity.Error);
|
||||
diagnostic.setSource("java-properties");
|
||||
diagnostic.setRange(doc.toRange(problem.getOffset(), problem.getLength()));
|
||||
return diagnostic;
|
||||
}).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static String createSyntaxErrorMessage(String parserMessage) {
|
||||
String message = parserMessage;
|
||||
if (parserMessage.contains("extraneous input '\\n' expecting")) {
|
||||
message = SYNTAX_ERROR_MSG__UNEXPECTED_END_OF_LINE;
|
||||
} else if (parserMessage.contains("mismatched input '<EOF>' expecting")) {
|
||||
message = YNTAX_ERROR_MSG__UNEXPECTED_END_OF_INPUT;
|
||||
}
|
||||
return SYNTAX_ERROR_HEADER_MSG + message;
|
||||
}
|
||||
// private void validateDocument(SimpleTextDocumentService documents, TextDocument doc) {
|
||||
// documents.publishDiagnostics(doc, parseResults.syntaxErrors.stream().map(problem -> {
|
||||
// DiagnosticImpl diagnostic = new DiagnosticImpl();
|
||||
// diagnostic.setMessage(createSyntaxErrorMessage(problem.getMessage()));
|
||||
// diagnostic.setCode(problem.getCode());
|
||||
// diagnostic.setSeverity(DiagnosticSeverity.Error);
|
||||
// diagnostic.setSource("java-properties");
|
||||
// diagnostic.setRange(doc.toRange(problem.getOffset(), problem.getLength()));
|
||||
// return diagnostic;
|
||||
// }).collect(Collectors.toList()));
|
||||
// }
|
||||
//
|
||||
// private static String createSyntaxErrorMessage(String parserMessage) {
|
||||
// String message = parserMessage;
|
||||
// if (parserMessage.contains("extraneous input '\\n' expecting")) {
|
||||
// message = SYNTAX_ERROR_MSG__UNEXPECTED_END_OF_LINE;
|
||||
// } else if (parserMessage.contains("mismatched input '<EOF>' expecting")) {
|
||||
// message = YNTAX_ERROR_MSG__UNEXPECTED_END_OF_INPUT;
|
||||
// }
|
||||
// return SYNTAX_ERROR_HEADER_MSG + message;
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected ServerCapabilitiesImpl getServerCapabilities() {
|
||||
@@ -84,4 +95,10 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer {
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
protected IReconcileEngine getReconcileEngine() {
|
||||
return new SpringPropertiesReconcileEngine(indexProvider, typeUtilProvider);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.DefaultSpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LoggingFormat;
|
||||
|
||||
import io.typefox.lsapi.services.json.LoggingJsonAdapter;
|
||||
@@ -125,7 +130,12 @@ public class Main {
|
||||
* When the request stream is closed, wait for 5s for all outstanding responses to compute, then return.
|
||||
*/
|
||||
public static void run(Connection connection) {
|
||||
ApplicationPropertiesLanguageServer server = new ApplicationPropertiesLanguageServer();
|
||||
SpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider();
|
||||
TypeUtil typeUtil = new TypeUtil(null);
|
||||
TypeUtilProvider typeUtilProvider = (IDocument doc) -> typeUtil;
|
||||
|
||||
ApplicationPropertiesLanguageServer server = new ApplicationPropertiesLanguageServer(indexProvider, typeUtilProvider);
|
||||
|
||||
LoggingJsonAdapter jsonServer = new LoggingJsonAdapter(server);
|
||||
jsonServer.setMessageLog(new PrintWriter(System.out));
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 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.vscode.application.properties.quickfix;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer;
|
||||
|
||||
import io.typefox.lsapi.CompletionItemKind;
|
||||
|
||||
public class ReplaceDeprecatedPropertyQuickfix implements ICompletionProposal {
|
||||
|
||||
public static ProblemFixer FIXER = (context, problem, proposals) -> {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
// PropertyInfo metadata = problem.getMetadata();
|
||||
// if (metadata!=null) {
|
||||
// String replacement = metadata.getDeprecationReplacement();
|
||||
// if (replacement!=null) {
|
||||
// //No need to check problem type... we only attach this fixer to problems of applicable type.
|
||||
// proposals.add(new ReplaceDeprecatedYamlQuickfix(context, problem));
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
@Override
|
||||
public ICompletionProposal deemphasize() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
@Override
|
||||
public String getLabel() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
// private final QuickfixContext context;
|
||||
// private final SpringPropertyProblem problem;
|
||||
//
|
||||
// private LazyProposalApplier applier = new LazyProposalApplier() {
|
||||
// protected ProposalApplier create() throws Exception {
|
||||
// String newName = problem.getMetadata().getDeprecationReplacement();
|
||||
// String oldName = problem.getPropertyName();
|
||||
// YamlPath newPath = YamlPath.fromProperty(newName);
|
||||
// YamlPath oldPath = YamlPath.fromProperty(oldName);
|
||||
// YamlPath prefix = newPath.commonPrefix(oldPath);
|
||||
// if (prefix.size()==newPath.size()-1 && newPath.size()==oldPath.size()) {
|
||||
// //only the last segment has changed. We can do a simple 'in-place' replace
|
||||
// // of just the change segment.
|
||||
// DocumentEdits edits = new DocumentEdits(context.getDocument());
|
||||
// edits.replace(problem.getOffset(), problem.getEnd(), newPath.getLastSegment().toPropString());
|
||||
// return edits;
|
||||
// }
|
||||
// YamlDocument doc = new YamlDocument(context.getDocument(), YamlStructureProvider.DEFAULT);
|
||||
// SNode problemNode = doc.getStructure().find(problem.getOffset());
|
||||
// if (problemNode.getNodeType()==SNodeType.KEY) {
|
||||
// SKeyNode problemKey = (SKeyNode) problemNode;
|
||||
// if (problemKey.isInKey(problem.getOffset())) {
|
||||
// YamlPathEdits edits = new YamlPathEdits(doc);
|
||||
//// print(doc, edits);
|
||||
// String valueText = problemKey.getValueWithRelativeIndent();
|
||||
// edits.deleteNode(problemKey);
|
||||
// int maxParentDeletions = oldPath.size() - prefix.size() - 1; // don't delete bits of the common prefix!
|
||||
// SChildBearingNode parent = problemNode.getParent();
|
||||
// while (maxParentDeletions>0 && parent!=null && parent.getChildren().size()==1) {
|
||||
// edits.deleteNode(parent);
|
||||
// parent = parent.getParent();
|
||||
// maxParentDeletions--;
|
||||
// }
|
||||
//// print(doc, edits);
|
||||
// SDocNode docRoot = problemNode.getDocNode(); //edits should stay within the same 'document' for yaml file that has multiple documents inside of it.
|
||||
// edits.createPath(docRoot, YamlPath.fromProperty(newName), valueText);
|
||||
//// print(doc, edits);
|
||||
// return edits;
|
||||
// }
|
||||
// }
|
||||
// //Not sure what to do... case not covered... so do nothing but tell the user.
|
||||
// context.getUI().error("Yaml file too complex",
|
||||
// "Sorry, but the yaml file is too complex for this quickfix. " +
|
||||
// "Please make the change manually."
|
||||
// );
|
||||
// return ProposalApplier.NULL;
|
||||
// }
|
||||
//
|
||||
//// private void print(YamlDocument doc, YamlPathEdits edits) throws Exception {
|
||||
//// Document workingCopy = new Document(doc.getDocument().get());
|
||||
//// edits.apply(workingCopy);
|
||||
//// System.out.println("==============");
|
||||
//// System.out.println(workingCopy.get());
|
||||
//// System.out.println("==============");
|
||||
//// }
|
||||
// };
|
||||
//
|
||||
// public ReplaceDeprecatedYamlQuickfix(QuickfixContext context, SpringPropertyProblem problem) {
|
||||
// this.context = context;
|
||||
// this.problem = problem;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void apply(IDocument doc) {
|
||||
// try {
|
||||
// applier.apply(doc);
|
||||
// } catch (Exception e) {
|
||||
// Log.log(e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private String getReplacementProperty() {
|
||||
// return problem.getMetadata().getDeprecationReplacement();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Point getSelection(IDocument doc) {
|
||||
// try {
|
||||
// return applier.getSelection(doc);
|
||||
// } catch (Exception e) {
|
||||
// Log.log(e);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String getAdditionalProposalInfo() {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String getDisplayString() {
|
||||
// return "Change to '"+getReplacementProperty()+"'";
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Image getImage() {
|
||||
// return JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public IContextInformation getContextInformation() {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -30,7 +30,8 @@ public enum ApplicationPropertiesProblemType implements ProblemType {
|
||||
PROP_INVALID_BEAN_PROPERTY("Accessing a named property in a type that doesn't provide a property accessor with that name"),
|
||||
PROP_UNKNOWN_PROPERTY(WARNING, "Property-key not found in any configuration metadata on the project's classpath"),
|
||||
PROP_DEPRECATED(WARNING, "Property is marked as Deprecated"),
|
||||
PROP_DUPLICATE_KEY("Multiple assignments to the same property value");
|
||||
PROP_DUPLICATE_KEY("Multiple assignments to the same property value"),
|
||||
PROP_SYNTAX_ERROR("Syntax Error");
|
||||
|
||||
private final ProblemSeverity defaultSeverity;
|
||||
private String description;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.springframework.ide.vscode.application.properties.reconcile;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
|
||||
/**
|
||||
* Helper class to reconcile text contained in a document region as a comma-separated list.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class DelimitedListReconciler {
|
||||
|
||||
interface TypeBasedReconciler {
|
||||
void reconcile(DocumentRegion region, Type expectType, IProblemCollector problems);
|
||||
}
|
||||
|
||||
private final TypeBasedReconciler valueReconciler;
|
||||
private final Pattern delimiter;
|
||||
|
||||
public DelimitedListReconciler(Pattern delimiter, TypeBasedReconciler valueReconciler) {
|
||||
this.valueReconciler = valueReconciler;
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
public void reconcile(DocumentRegion region, Type listType, IProblemCollector problems) {
|
||||
Type elType = getElementType(listType);
|
||||
//Its pointless to reconcile list of we can't determine value type.
|
||||
if (elType!=null) {
|
||||
Arrays.stream(region.split(delimiter)).forEach(entry -> {
|
||||
valueReconciler.reconcile(entry, elType, problems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Type getElementType(Type listType) {
|
||||
Type elType = TypeUtil.getDomainType(listType);
|
||||
if (elType!=null) {
|
||||
Type nestedElType = getElementType(elType);
|
||||
if (nestedElType!=null) {
|
||||
return nestedElType;
|
||||
}
|
||||
return elType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.springframework.ide.vscode.application.properties.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.application.properties.reconcile.ApplicationPropertiesProblemType.PROP_DUPLICATE_KEY;
|
||||
import static org.springframework.ide.vscode.application.properties.reconcile.SpringPropertyProblem.problem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
|
||||
|
||||
/**
|
||||
* Instance of this class is fed the regions of names in a properties file, checks them for duplicates and
|
||||
* reports the duplicates to {@link IProblemCollector}.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class DuplicateNameChecker {
|
||||
|
||||
/**
|
||||
* Keep track of seen names. The value in the map entries is either null
|
||||
* or the Region for the first time the name was seen.
|
||||
* <p>
|
||||
* This is used so that the first occurrence can still be reported retroactively
|
||||
* when the second occurrence is encountered.
|
||||
*/
|
||||
private Map<String, Key> seen = new HashMap<>();
|
||||
|
||||
IProblemCollector problems;
|
||||
|
||||
public DuplicateNameChecker(IProblemCollector problems) {
|
||||
this.problems = problems;
|
||||
}
|
||||
|
||||
public void check(Key nameRegion) {
|
||||
String name = nameRegion.decode();
|
||||
if (!name.isEmpty()) {
|
||||
if (seen.containsKey(name)) {
|
||||
Key pending = seen.get(name);
|
||||
if (pending!=null) {
|
||||
reportDuplicate(pending);
|
||||
seen.put(name, null);
|
||||
}
|
||||
reportDuplicate(nameRegion);
|
||||
} else {
|
||||
seen.put(name, nameRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reportDuplicate(Key nameRegion) {
|
||||
String decodedKey = nameRegion.decode();
|
||||
problems.accept(problem(PROP_DUPLICATE_KEY,
|
||||
"Duplicate property '"+decodedKey+"'", nameRegion));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package org.springframework.ide.vscode.application.properties.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.application.properties.reconcile.SpringPropertyProblem.problem;
|
||||
import static org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.isBracketable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
|
||||
|
||||
/**
|
||||
* Helper class for {@link SpringPropertiesReconcileEngine} and {@link SpringPropertiesCompletionEngine}.
|
||||
* <p>
|
||||
* This class provides a means to 'navigate' a chain of bracket and dot navigation operations down
|
||||
* from a typed property into its value type.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class PropertyNavigator {
|
||||
|
||||
private static final char EOF = 0;
|
||||
|
||||
/**
|
||||
* If problem collector is not null, then problems detected in the navigation chain are added to the
|
||||
* collector.
|
||||
*/
|
||||
private IProblemCollector problemCollector;
|
||||
|
||||
/**
|
||||
* Document in which navigation chain text is contained.
|
||||
*/
|
||||
private IDocument doc;
|
||||
|
||||
private TypeUtil typeUtil;
|
||||
|
||||
private Node region;
|
||||
|
||||
private String regionText;
|
||||
|
||||
public PropertyNavigator(IDocument doc, IProblemCollector problemCollector, TypeUtil typeUtil, Node region) throws BadLocationException {
|
||||
this.doc = doc;
|
||||
this.problemCollector = problemCollector==null?IProblemCollector.NULL:problemCollector;
|
||||
this.typeUtil = typeUtil;
|
||||
this.region = region;
|
||||
this.regionText = doc.get(region.getOffset(), region.getLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param offset current position in the nav chain. Text before this offset is already 'processed'.
|
||||
* @param type The type at the end of the already processed nav chain. The next nav op in the chain
|
||||
* should go deeper into this type.
|
||||
* @param r The entire region of the navchain, including both the already processed portion as well
|
||||
* as the remaining text.
|
||||
* @return Type at the end of the whole nav chain, or null if the type could not be determined.
|
||||
*/
|
||||
public Type navigate(int offset, Type type) {
|
||||
if (type!=null) {
|
||||
if (offset<getEnd(region)) {
|
||||
char navOp = getChar(offset);
|
||||
if (navOp=='.') {
|
||||
if (typeUtil.isDotable(type)) {
|
||||
return dotNavigate(offset, type);
|
||||
} else {
|
||||
problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_INVALID_BEAN_NAVIGATION,
|
||||
"Can't use '.' navigation for property '"+textBetween(region.getOffset(), offset)+"' of type "+type,
|
||||
offset, getEnd(region)-offset));
|
||||
}
|
||||
} else if (navOp=='[') {
|
||||
if (isBracketable(type)) {
|
||||
return bracketNavigate(offset, type);
|
||||
} else {
|
||||
problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_INVALID_INDEXED_NAVIGATION,
|
||||
"Can't use '[..]' navigation for property '"+textBetween(region.getOffset(), offset)+"' of type "+type,
|
||||
offset, getEnd(region)-offset));
|
||||
}
|
||||
} else {
|
||||
problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_EXPECTED_DOT_OR_LBRACK, "Expecting either a '.' or '['", offset, getEnd(region)-offset));
|
||||
}
|
||||
} else {
|
||||
//end of nav chain
|
||||
return type;
|
||||
}
|
||||
}
|
||||
//Something we can't handle...
|
||||
return null;
|
||||
}
|
||||
|
||||
private String textBetween(int start, int end) {
|
||||
try {
|
||||
if (end>start) {
|
||||
return doc.get(start, end-start);
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
//ignore
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private int indexOf(char c, int from) {
|
||||
int offset = region.getOffset();
|
||||
int found = regionText.indexOf(c, from-offset);
|
||||
if (found>=0) {
|
||||
return found+offset;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bracket navigation into given type, after a bracket at
|
||||
* was found at given offset. Assumes the type has already been checked to
|
||||
* be 'bracketable'.
|
||||
*/
|
||||
private Type bracketNavigate(int offset, Type type) {
|
||||
int lbrack = offset;
|
||||
int rbrack = indexOf(']', lbrack);
|
||||
if (rbrack<0) {
|
||||
problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_NO_MATCHING_RBRACK,
|
||||
"No matching ']'",
|
||||
offset, 1));
|
||||
} else {
|
||||
String indexStr = textBetween(lbrack+1, rbrack);
|
||||
if (!indexStr.contains("${")) {
|
||||
try {
|
||||
Integer.parseInt(indexStr);
|
||||
} catch (Exception e) {
|
||||
problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_NON_INTEGER_IN_BRACKETS,
|
||||
"Expecting 'Integer' for '[...]' notation '"+textBetween(region.getOffset(), lbrack)+"'",
|
||||
lbrack+1, rbrack-lbrack-1
|
||||
));
|
||||
}
|
||||
}
|
||||
Type domainType = TypeUtil.getDomainType(type);
|
||||
return navigate(rbrack+1, domainType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle dot navigation into given type, after a '.' was
|
||||
* was found at given offset. Assumes the type has already been
|
||||
* checked to be 'dotable'.
|
||||
*/
|
||||
private Type dotNavigate(int offset, Type type) {
|
||||
if (TypeUtil.isMap(type)) {
|
||||
int keyStart = offset+1;
|
||||
Type domainType = TypeUtil.getDomainType(type);
|
||||
int keyEnd = -1;
|
||||
if (typeUtil.isDotable(domainType)) {
|
||||
//'.' should be interpreted as navigation.
|
||||
keyEnd = nextNavOp(".[", offset+1);
|
||||
} else {
|
||||
//'.' should *not* be interpreted as navigation.
|
||||
keyEnd = nextNavOp("[", offset+1);
|
||||
}
|
||||
String key = textBetween(keyStart, keyEnd);
|
||||
Type keyType = typeUtil.getKeyType(type);
|
||||
if (keyType!=null) {
|
||||
ValueParser keyParser = typeUtil.getValueParser(keyType);
|
||||
if (keyParser!=null) {
|
||||
try {
|
||||
keyParser.parse(key);
|
||||
} catch (Exception e) {
|
||||
problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_VALUE_TYPE_MISMATCH,
|
||||
"Expecting "+typeUtil.niceTypeName(keyType),
|
||||
keyStart, keyEnd-keyStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
return navigate(keyEnd, domainType);
|
||||
} else {
|
||||
// dot navigation into object properties
|
||||
int keyStart = offset+1;
|
||||
int keyEnd = nextNavOp(".[", offset+1);
|
||||
if (keyEnd<0) {
|
||||
keyEnd = getEnd(region);
|
||||
}
|
||||
String key = StringUtil.camelCaseToHyphens(textBetween(keyStart, keyEnd));
|
||||
|
||||
List<TypedProperty> properties = typeUtil.getProperties(type, EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
|
||||
if (properties!=null) {
|
||||
TypedProperty prop = null;
|
||||
for (TypedProperty p : properties) {
|
||||
if (p.getName().equals(key)) {
|
||||
prop = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prop==null) {
|
||||
problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_INVALID_BEAN_PROPERTY,
|
||||
"Type '"+typeUtil.niceTypeName(type)+"' has no property '"+key+"'",
|
||||
keyStart, keyEnd-keyStart));
|
||||
} else {
|
||||
if (prop.isDeprecated()) {
|
||||
problemCollector.accept(problemDeprecated(type, prop, keyStart, keyEnd-keyStart));
|
||||
}
|
||||
return navigate(keyEnd, prop.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ReconcileProblem problemDeprecated(Type contextType, TypedProperty prop, int offset, int len) {
|
||||
SpringPropertyProblem p = problem(ApplicationPropertiesProblemType.PROP_DEPRECATED,
|
||||
TypeUtil.deprecatedPropertyMessage(
|
||||
prop.getName(), typeUtil.niceTypeName(contextType),
|
||||
prop.getDeprecationReplacement(), prop.getDeprecationReason()
|
||||
),
|
||||
offset, len
|
||||
);
|
||||
p.setPropertyName(prop.getName());
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip ahead from give position until reaching the next 'navigation' operator (or the end
|
||||
* of the navigation chain region).
|
||||
*
|
||||
* @param navops Each character in this string is considered a 'navigation operator'.
|
||||
* @param pos current position in the document.
|
||||
* @return position of next navop if found, or the position at the end of the region if not found.
|
||||
*/
|
||||
private int nextNavOp(String navops, int pos) {
|
||||
int end = getEnd(region);
|
||||
while (pos < end && navops.indexOf(getChar(pos))<0) {
|
||||
pos++;
|
||||
}
|
||||
return Math.min(pos, end); //ensure never past the end
|
||||
}
|
||||
|
||||
private char getChar(int offset) {
|
||||
try {
|
||||
return doc.getChar(offset);
|
||||
} catch (BadLocationException e) {
|
||||
//outside doc, return something anyways.
|
||||
return EOF;
|
||||
}
|
||||
}
|
||||
|
||||
private int getEnd(Node region) {
|
||||
return region.getOffset()+region.getLength();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014-2016 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.vscode.application.properties.reconcile;
|
||||
|
||||
//import static org.springframework.ide.eclipse.boot.properties.editor.SpringPropertiesCompletionEngine.isAssign;
|
||||
import static org.springframework.ide.vscode.application.properties.reconcile.ApplicationPropertiesProblemType.PROP_DEPRECATED;
|
||||
import static org.springframework.ide.vscode.application.properties.reconcile.ApplicationPropertiesProblemType.PROP_SYNTAX_ERROR;
|
||||
import static org.springframework.ide.vscode.application.properties.reconcile.ApplicationPropertiesProblemType.PROP_UNKNOWN_PROPERTY;
|
||||
import static org.springframework.ide.vscode.application.properties.reconcile.SpringPropertyProblem.problem;
|
||||
import static org.springframework.ide.vscode.commons.util.StringUtil.commonPrefix;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndex;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.application.properties.quickfix.ReplaceDeprecatedPropertyQuickfix;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
|
||||
import org.springframework.ide.vscode.java.properties.parser.Parser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.KeyValuePair;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesFileEscapes;
|
||||
|
||||
/**
|
||||
* Implements reconciling algorithm for {@link SpringPropertiesReconcileStrategy}.
|
||||
* <p>
|
||||
* The code in here could have been also part of the {@link SpringPropertiesReconcileStrategy}
|
||||
* itself, however isolating it here allows it to me more easily unit tested (no dependencies
|
||||
* on ISourceViewer which is difficult to 'mock' in testing harness.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class SpringPropertiesReconcileEngine implements IReconcileEngine {
|
||||
|
||||
/**
|
||||
* Regexp that matches a ',' surrounded by whitespace, including escaped whitespace / newlines
|
||||
*/
|
||||
private static final Pattern COMMA = Pattern.compile(
|
||||
"(\\s|\\\\\\s)*,(\\s|\\\\\\s)*"
|
||||
);
|
||||
|
||||
// private static final Pattern SPACES = Pattern.compile(
|
||||
// "(\\s|\\\\\\s)*"
|
||||
// );
|
||||
|
||||
/**
|
||||
* Regexp that matches a whitespace, including escaped whitespace
|
||||
*/
|
||||
// private static final Pattern ASSIGN = SpringPropertiesCompletionEngine.ASSIGN;
|
||||
|
||||
private SpringPropertyIndexProvider fIndexProvider;
|
||||
private TypeUtilProvider typeUtilProvider;
|
||||
private final DelimitedListReconciler commaListReconciler = new DelimitedListReconciler(COMMA, this::reconcileType);
|
||||
private Parser parser = new AntlrParser();
|
||||
|
||||
public SpringPropertiesReconcileEngine(SpringPropertyIndexProvider provider, TypeUtilProvider typeUtilProvider) {
|
||||
this.fIndexProvider = provider;
|
||||
this.typeUtilProvider = typeUtilProvider;
|
||||
}
|
||||
|
||||
public void reconcile(IDocument doc, IProblemCollector problemCollector) {
|
||||
FuzzyMap<PropertyInfo> index = fIndexProvider.getIndex(doc);
|
||||
problemCollector.beginCollecting();
|
||||
try {
|
||||
ParseResults results = parser.parse(doc.get());
|
||||
DuplicateNameChecker duplicateNameChecker = new DuplicateNameChecker(problemCollector);
|
||||
|
||||
results.syntaxErrors.forEach(syntaxError -> {
|
||||
problemCollector.accept(problem(PROP_SYNTAX_ERROR, syntaxError.getMessage(), syntaxError.getOffset(),
|
||||
syntaxError.getLength()));
|
||||
});
|
||||
|
||||
if (index==null || index.isEmpty()) {
|
||||
//don't report errors when index is empty, simply don't check (otherwise we will just reprot
|
||||
// all properties as errors, but this not really useful information since the cause is
|
||||
// some problem putting information about properties into the index.
|
||||
return;
|
||||
}
|
||||
|
||||
results.ast.getNodes(KeyValuePair.class).forEach(pair -> {
|
||||
Key fullName = pair.getKey();
|
||||
String keyName = fullName.decode();
|
||||
duplicateNameChecker.check(fullName);
|
||||
PropertyInfo validProperty = SpringPropertyIndex.findLongestValidProperty(index, keyName);
|
||||
try {
|
||||
if (validProperty!=null) {
|
||||
//TODO: Remove last remnants of 'IRegion trimmedRegion' here and replace
|
||||
// it all with just passing around 'fullName' DocumentRegion. This may require changes
|
||||
// in PropertyNavigator (probably these changes are also for the better making it simpler as well)
|
||||
if (validProperty.isDeprecated()) {
|
||||
problemCollector.accept(problemDeprecated(fullName, validProperty));
|
||||
}
|
||||
int offset = validProperty.getId().length() + fullName.getOffset();
|
||||
PropertyNavigator navigator = new PropertyNavigator(doc, problemCollector, typeUtilProvider.getTypeUtil(doc), fullName);
|
||||
Type valueType = navigator.navigate(offset, TypeParser.parse(validProperty.getType()));
|
||||
if (valueType!=null) {
|
||||
reconcileType(doc, valueType, pair.getValue(), problemCollector);
|
||||
}
|
||||
} else { //validProperty==null
|
||||
//The name is invalid, with no 'prefix' of the name being a valid property name.
|
||||
PropertyInfo similarEntry = index.findLongestCommonPrefixEntry(fullName.toString());
|
||||
CharSequence validPrefix = commonPrefix(similarEntry.getId(), keyName);
|
||||
problemCollector.accept(problemUnkownProperty(createRegion(doc, fullName), similarEntry, validPrefix));
|
||||
} //end: validProperty==null
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
});
|
||||
} catch (Throwable e2) {
|
||||
Log.log(e2);
|
||||
} finally {
|
||||
problemCollector.endCollecting();
|
||||
}
|
||||
}
|
||||
|
||||
protected SpringPropertyProblem problemDeprecated(Key key, PropertyInfo property) {
|
||||
SpringPropertyProblem p = problem(PROP_DEPRECATED,
|
||||
TypeUtil.deprecatedPropertyMessage(
|
||||
property.getId(), null,
|
||||
property.getDeprecationReplacement(),
|
||||
property.getDeprecationReason()
|
||||
),
|
||||
key
|
||||
);
|
||||
p.setPropertyName(property.getId());
|
||||
p.setMetadata(property);
|
||||
p.setProblemFixer(ReplaceDeprecatedPropertyQuickfix.FIXER);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected SpringPropertyProblem problemUnkownProperty(DocumentRegion fullNameRegion,
|
||||
PropertyInfo similarEntry, CharSequence validPrefix) {
|
||||
String fullName = fullNameRegion.toString();
|
||||
SpringPropertyProblem p = problem(PROP_UNKNOWN_PROPERTY,
|
||||
"'"+fullName+"' is an unknown property."+suggestSimilar(similarEntry, validPrefix, fullName),
|
||||
fullNameRegion.subSequence(validPrefix.length())
|
||||
);
|
||||
p.setPropertyName(fullName);
|
||||
return p;
|
||||
}
|
||||
|
||||
private void reconcileType(IDocument doc, Type expectType, Node value, IProblemCollector problems) {
|
||||
// DocumentRegion escapedValue = getAssignedValue(doc, regions, i);
|
||||
// if (escapedValue==null) {
|
||||
// int charPos = DocumentUtil.lastNonWhitespaceCharOfRegion(doc, regions[i]);
|
||||
// if (charPos>=0) {
|
||||
// problems.accept(problem(SpringPropertiesProblemType.PROP_VALUE_TYPE_MISMATCH,
|
||||
// "Expecting '"+typeUtil.niceTypeName(expectType)+"'",
|
||||
// charPos, 1));
|
||||
// }
|
||||
// } else {
|
||||
// reconcileType(escapedValue, expectType, problems);
|
||||
// }
|
||||
reconcileType(createRegion(doc, value), expectType,
|
||||
problems);
|
||||
}
|
||||
|
||||
private DocumentRegion createRegion(IDocument doc, Node value) {
|
||||
return new DocumentRegion(doc, value.getOffset(), value.getOffset() + value.getLength());
|
||||
}
|
||||
|
||||
private void reconcileType(DocumentRegion region, Type expectType, IProblemCollector problems) {
|
||||
TypeUtil typeUtil = typeUtilProvider.getTypeUtil(region.getDocument());
|
||||
ValueParser parser = typeUtil.getValueParser(expectType);
|
||||
if (parser!=null) {
|
||||
try {
|
||||
String valueStr = PropertiesFileEscapes.unescape(region.toString());
|
||||
if (!valueStr.contains("${")) {
|
||||
//Don't check strings that look like they use variable substitution.
|
||||
parser.parse(valueStr);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
problems.accept(problem(ApplicationPropertiesProblemType.PROP_VALUE_TYPE_MISMATCH,
|
||||
"Expecting '"+typeUtil.niceTypeName(expectType)+"'",
|
||||
region));
|
||||
}
|
||||
} else if (TypeUtil.isList(expectType)||TypeUtil.isArray(expectType)) {
|
||||
commaListReconciler.reconcile(region, expectType, problems);
|
||||
}
|
||||
}
|
||||
|
||||
// private DocumentRegion getAssignedValue(IDocument doc, ITypedRegion[] regions, int i) {
|
||||
// int valueRegionIndex = i+1;
|
||||
// if (valueRegionIndex<regions.length) {
|
||||
// String valueRegionType = regions[valueRegionIndex].getType();
|
||||
// DocumentRegion valueRegion = new DocumentRegion(doc, regions[valueRegionIndex]);
|
||||
// if (IPropertiesFilePartitions.PROPERTY_VALUE.equals(valueRegionType)) {
|
||||
// //Need to remove the 'ASSIGN' bit from the start
|
||||
// valueRegion = valueRegion.trimStart(ASSIGN).trimEnd(SPACES);
|
||||
// //region text includes
|
||||
// // potential padding with whitespace.
|
||||
// // the ':' or '=' (if its there).
|
||||
// return valueRegion;
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
private String suggestSimilar(PropertyInfo similarEntry, CharSequence validPrefix, CharSequence fullName) {
|
||||
int matchedChars = validPrefix.length();
|
||||
int wrongChars = fullName.length()-matchedChars;
|
||||
if (wrongChars<matchedChars) {
|
||||
return " Did you mean '"+similarEntry.getId()+"'?";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that there is an assignment char directly following the given region.
|
||||
*/
|
||||
// private boolean isAssigned(IDocument doc, IRegion r) {
|
||||
// try {
|
||||
// char c = doc.getChar(r.getOffset()+r.getLength());
|
||||
// //Note either a '=' or a ':' can be used to assign properties.
|
||||
// return isAssign(c);
|
||||
// } catch (BadLocationException e) {
|
||||
// //happens if looking for assignment char outside the document
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.springframework.ide.vscode.application.properties.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
|
||||
|
||||
// TODO: Move to a common project shared between YAML and Properties
|
||||
|
||||
public class SpringPropertyProblem extends ReconcileProblemImpl {
|
||||
|
||||
private PropertyInfo property = null;
|
||||
private ProblemFixer fixer;
|
||||
private String propertyName;
|
||||
|
||||
public SpringPropertyProblem(ProblemType type, String msg, int offset, int len) {
|
||||
super(type, msg, offset, len);
|
||||
}
|
||||
|
||||
public static SpringPropertyProblem problem(ApplicationPropertiesProblemType type, String msg, int offset, int len) {
|
||||
return new SpringPropertyProblem(type, msg, offset, len);
|
||||
}
|
||||
|
||||
public static SpringPropertyProblem problem(ApplicationPropertiesProblemType type, String msg, Node region) {
|
||||
return new SpringPropertyProblem(type, msg, region.getOffset(), region.getLength());
|
||||
}
|
||||
|
||||
public static SpringPropertyProblem problem(ApplicationPropertiesProblemType type, String msg, DocumentRegion region) {
|
||||
return new SpringPropertyProblem(type, msg, region.getStart(), region.getLength());
|
||||
}
|
||||
|
||||
public void setMetadata(PropertyInfo property) {
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public void setProblemFixer(ProblemFixer fixer) {
|
||||
this.fixer = fixer;
|
||||
}
|
||||
|
||||
public void setPropertyName(String name) {
|
||||
propertyName = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,24 +48,15 @@ import io.typefox.lsapi.Diagnostic;
|
||||
*/
|
||||
public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
|
||||
|
||||
private static final String SYNTAX_ERROR__UNEXPECTED_END_OF_INPUT = "Unexpected end of input, value identifier is expected";
|
||||
private static final String SYNTAX_ERROR__UNEXPECTED_END_OF_LINE = "Unexpected end of line, value identifier is expected";
|
||||
|
||||
@Test public void testReconcileCatchesParseError() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new);
|
||||
harness.intialize(null);
|
||||
|
||||
Editor editor = harness.newEditor("key\n");
|
||||
editor.assertProblems("key|" + SYNTAX_ERROR__UNEXPECTED_END_OF_LINE);
|
||||
Editor editor = newEditor("key\n");
|
||||
editor.assertProblems("key|extraneous input");
|
||||
}
|
||||
|
||||
@Test public void linterRunsOnDocumentOpenAndChange() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new);
|
||||
harness.intialize(null);
|
||||
Editor editor = newEditor("key");
|
||||
|
||||
Editor editor = harness.newEditor("key");
|
||||
|
||||
editor.assertProblems("key|" + SYNTAX_ERROR__UNEXPECTED_END_OF_INPUT);
|
||||
editor.assertProblems("key|mismatched input");
|
||||
|
||||
editor.setText(
|
||||
"problem\n" +
|
||||
@@ -73,7 +64,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
|
||||
"another"
|
||||
);
|
||||
|
||||
editor.assertProblems("problem|" + SYNTAX_ERROR__UNEXPECTED_END_OF_LINE, "another|" + SYNTAX_ERROR__UNEXPECTED_END_OF_INPUT);
|
||||
editor.assertProblems("problem|extraneous input", "another|mismatched input");
|
||||
}
|
||||
|
||||
|
||||
@@ -1563,7 +1554,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
|
||||
|
||||
@Override
|
||||
protected SimpleLanguageServer newLanguageServer() {
|
||||
return new ApplicationPropertiesLanguageServer();
|
||||
return new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,14 +15,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.application.properties.ApplicationPropertiesLanguageServer;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
|
||||
|
||||
import io.typefox.lsapi.InitializeResult;
|
||||
import io.typefox.lsapi.ServerCapabilities;
|
||||
import io.typefox.lsapi.TextDocumentSyncKind;
|
||||
import io.typefox.lsapi.services.LanguageServer;
|
||||
|
||||
/**
|
||||
* Boot app properties file language server tests
|
||||
@@ -36,9 +37,14 @@ public class ApplicationPropertiesLanguageServerTest {
|
||||
return Paths.get(ApplicationPropertiesLanguageServer.class.getResource(name).toURI()).toFile();
|
||||
}
|
||||
|
||||
private LanguageServerHarness newHarness() throws Exception {
|
||||
Callable<? extends LanguageServer> f = () -> new ApplicationPropertiesLanguageServer((d) -> null, (d) -> null);
|
||||
return new LanguageServerHarness(f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndInitializeServerWithWorkspace() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new);
|
||||
LanguageServerHarness harness = newHarness();
|
||||
File workspaceRoot = getTestResource("/workspace/");
|
||||
assertExpectedInitResult(harness.intialize(workspaceRoot));
|
||||
}
|
||||
@@ -46,7 +52,7 @@ public class ApplicationPropertiesLanguageServerTest {
|
||||
@Test
|
||||
public void createAndInitializeServerWithoutWorkspace() throws Exception {
|
||||
File workspaceRoot = null;
|
||||
LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new);
|
||||
LanguageServerHarness harness = newHarness();
|
||||
assertExpectedInitResult(harness.intialize(workspaceRoot));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.net.Socket;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.DefaultSpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
|
||||
Reference in New Issue
Block a user