mirror of
https://github.com/lightbend/config.git
synced 2025-03-17 04:40:41 +08:00
Improve error messages to suggest quoting strings or using .properties
Also, when displaying a token use a more user-friendly format like ',' instead of COMMA
This commit is contained in:
parent
63b920acd1
commit
005cd6da2c
@ -41,6 +41,10 @@ final class Parser {
|
||||
final private ConfigSyntax flavor;
|
||||
final private ConfigOrigin baseOrigin;
|
||||
final private LinkedList<Path> pathStack;
|
||||
// this is the number of "equals" we are inside,
|
||||
// used to modify the error message to reflect that
|
||||
// someone may think this is .properties format.
|
||||
int equalsCount;
|
||||
|
||||
ParseContext(ConfigSyntax flavor, ConfigOrigin origin,
|
||||
Iterator<Token> tokens, ConfigIncluder includer,
|
||||
@ -53,6 +57,7 @@ final class Parser {
|
||||
this.includer = includer;
|
||||
this.includeContext = includeContext;
|
||||
this.pathStack = new LinkedList<Path>();
|
||||
this.equalsCount = 0;
|
||||
}
|
||||
|
||||
private Token nextToken() {
|
||||
@ -233,6 +238,49 @@ final class Parser {
|
||||
return new ConfigException.Parse(lineOrigin(), message, cause);
|
||||
}
|
||||
|
||||
|
||||
private String addQuoteSuggestion(String badToken, String message) {
|
||||
return addQuoteSuggestion(null, equalsCount > 0, badToken, message);
|
||||
}
|
||||
|
||||
private String addQuoteSuggestion(Path lastPath, boolean insideEquals, String badToken,
|
||||
String message) {
|
||||
String previousFieldName;
|
||||
if (lastPath != null) {
|
||||
previousFieldName = lastPath.render();
|
||||
} else if (pathStack.isEmpty())
|
||||
previousFieldName = null;
|
||||
else
|
||||
previousFieldName = pathStack.peek().render();
|
||||
|
||||
String part;
|
||||
if (badToken.equals(Tokens.END.toString())) {
|
||||
// EOF requires special handling for the error to make sense.
|
||||
if (previousFieldName != null)
|
||||
part = message + " (if you intended '" + previousFieldName
|
||||
+ "' to be part of a value, instead of a key, "
|
||||
+ "try adding double quotes around the whole value";
|
||||
else
|
||||
return message;
|
||||
} else {
|
||||
if (previousFieldName != null) {
|
||||
part = message + " (if you intended " + badToken
|
||||
+ " to be part of the value for '" + previousFieldName + "', "
|
||||
+ "try enclosing the value in double quotes";
|
||||
} else {
|
||||
part = message + " (if you intended " + badToken
|
||||
+ " to be part of the value for "
|
||||
+ "a setting, try enclosing the value in double quotes";
|
||||
}
|
||||
}
|
||||
|
||||
if (insideEquals)
|
||||
return part
|
||||
+ ", or you may be able to rename the file .properties rather than .conf)";
|
||||
else
|
||||
return part + ")";
|
||||
}
|
||||
|
||||
private AbstractConfigValue parseValue(Token token) {
|
||||
if (Tokens.isValue(token)) {
|
||||
return Tokens.getValue(token);
|
||||
@ -241,8 +289,8 @@ final class Parser {
|
||||
} else if (token == Tokens.OPEN_SQUARE) {
|
||||
return parseArray();
|
||||
} else {
|
||||
throw parseError("Expecting a value but got wrong token: "
|
||||
+ token);
|
||||
throw parseError(addQuoteSuggestion(token.toString(),
|
||||
"Expecting a value but got wrong token: " + token));
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,7 +331,7 @@ final class Parser {
|
||||
String key = (String) Tokens.getValue(token).unwrapped();
|
||||
return Path.newKey(key);
|
||||
} else {
|
||||
throw parseError("Expecting close brace } or a field name, got "
|
||||
throw parseError("Expecting close brace } or a field name here, got "
|
||||
+ token);
|
||||
}
|
||||
} else {
|
||||
@ -293,6 +341,11 @@ final class Parser {
|
||||
expression.add(t);
|
||||
t = nextToken(); // note: don't cross a newline
|
||||
}
|
||||
|
||||
if (expression.isEmpty()) {
|
||||
throw parseError("expecting a close brace or a field name here, got " + t);
|
||||
}
|
||||
|
||||
putBack(t); // put back the token we ended with
|
||||
return parsePathExpression(expression.iterator(), lineOrigin());
|
||||
}
|
||||
@ -362,13 +415,18 @@ final class Parser {
|
||||
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
||||
ConfigOrigin objectOrigin = lineOrigin();
|
||||
boolean afterComma = false;
|
||||
Path lastPath = null;
|
||||
boolean lastInsideEquals = false;
|
||||
|
||||
while (true) {
|
||||
Token t = nextTokenIgnoringNewline();
|
||||
if (t == Tokens.CLOSE_CURLY) {
|
||||
if (flavor == ConfigSyntax.JSON && afterComma) {
|
||||
throw parseError("expecting a field name after comma, got a close brace }");
|
||||
throw parseError(addQuoteSuggestion(t.toString(),
|
||||
"expecting a field name after a comma, got a close brace } instead"));
|
||||
} else if (!hadOpenCurly) {
|
||||
throw parseError("unbalanced close brace '}' with no open brace");
|
||||
throw parseError(addQuoteSuggestion(t.toString(),
|
||||
"unbalanced close brace '}' with no open brace"));
|
||||
}
|
||||
break;
|
||||
} else if (t == Tokens.END && !hadOpenCurly) {
|
||||
@ -381,6 +439,7 @@ final class Parser {
|
||||
} else {
|
||||
Path path = parseKey(t);
|
||||
Token afterKey = nextTokenIgnoringNewline();
|
||||
boolean insideEquals = false;
|
||||
|
||||
// path must be on-stack while we parse the value
|
||||
pathStack.push(path);
|
||||
@ -394,8 +453,14 @@ final class Parser {
|
||||
newValue = parseObject(true);
|
||||
} else {
|
||||
if (!isKeyValueSeparatorToken(afterKey)) {
|
||||
throw parseError("Key may not be followed by token: "
|
||||
+ afterKey);
|
||||
throw parseError(addQuoteSuggestion(afterKey.toString(),
|
||||
"Key '" + path.render() + "' may not be followed by token: "
|
||||
+ afterKey));
|
||||
}
|
||||
|
||||
if (afterKey == Tokens.EQUALS) {
|
||||
insideEquals = true;
|
||||
equalsCount += 1;
|
||||
}
|
||||
|
||||
consolidateValueTokens();
|
||||
@ -403,7 +468,11 @@ final class Parser {
|
||||
newValue = parseValue(valueToken);
|
||||
}
|
||||
|
||||
pathStack.pop();
|
||||
lastPath = pathStack.pop();
|
||||
if (insideEquals) {
|
||||
equalsCount -= 1;
|
||||
}
|
||||
lastInsideEquals = insideEquals;
|
||||
|
||||
String key = path.first();
|
||||
Path remaining = path.remainder();
|
||||
@ -451,25 +520,25 @@ final class Parser {
|
||||
t = nextTokenIgnoringNewline();
|
||||
if (t == Tokens.CLOSE_CURLY) {
|
||||
if (!hadOpenCurly) {
|
||||
throw parseError("unbalanced close brace '}' with no open brace");
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
t.toString(), "unbalanced close brace '}' with no open brace"));
|
||||
}
|
||||
break;
|
||||
} else if (hadOpenCurly) {
|
||||
throw parseError("Expecting close brace } or a comma, got "
|
||||
+ t);
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
t.toString(), "Expecting close brace } or a comma, got " + t));
|
||||
} else {
|
||||
if (t == Tokens.END) {
|
||||
putBack(t);
|
||||
break;
|
||||
} else {
|
||||
throw parseError("Expecting end of input or a comma, got "
|
||||
+ t);
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
t.toString(), "Expecting end of input or a comma, got " + t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new SimpleConfigObject(objectOrigin,
|
||||
values);
|
||||
return new SimpleConfigObject(objectOrigin, values);
|
||||
}
|
||||
|
||||
private SimpleConfigList parseArray() {
|
||||
@ -493,7 +562,10 @@ final class Parser {
|
||||
values.add(parseArray());
|
||||
} else {
|
||||
throw parseError("List should have ] or a first element after the open [, instead had token: "
|
||||
+ t);
|
||||
+ t
|
||||
+ " (if you want "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)");
|
||||
}
|
||||
|
||||
// now remaining elements
|
||||
@ -507,7 +579,10 @@ final class Parser {
|
||||
return new SimpleConfigList(arrayOrigin, values);
|
||||
} else {
|
||||
throw parseError("List should have ended with ] or had a comma, instead had token: "
|
||||
+ t);
|
||||
+ t
|
||||
+ " (if you want "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -527,7 +602,10 @@ final class Parser {
|
||||
putBack(t);
|
||||
} else {
|
||||
throw parseError("List should have had new element after a comma, instead had token: "
|
||||
+ t);
|
||||
+ t
|
||||
+ " (if you want the comma or "
|
||||
+ t
|
||||
+ " to be part of a string value, then double-quote it)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -659,9 +737,12 @@ final class Parser {
|
||||
} else if (Tokens.isUnquotedText(t)) {
|
||||
text = Tokens.getUnquotedText(t);
|
||||
} else {
|
||||
throw new ConfigException.BadPath(origin, originalText,
|
||||
throw new ConfigException.BadPath(
|
||||
origin,
|
||||
originalText,
|
||||
"Token not allowed in path expression: "
|
||||
+ t);
|
||||
+ t
|
||||
+ " (you can double-quote this token if you really want it here)");
|
||||
}
|
||||
|
||||
addPathText(buf, false, text);
|
||||
|
@ -5,18 +5,28 @@ package com.typesafe.config.impl;
|
||||
|
||||
class Token {
|
||||
final private TokenType tokenType;
|
||||
final private String debugString;
|
||||
|
||||
Token(TokenType tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
this(tokenType, null);
|
||||
}
|
||||
|
||||
Token(TokenType tokenType, String debugString) {
|
||||
this.tokenType = tokenType;
|
||||
this.debugString = debugString;
|
||||
}
|
||||
|
||||
|
||||
public TokenType tokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return tokenType.name();
|
||||
if (debugString != null)
|
||||
return debugString;
|
||||
else
|
||||
return tokenType.name();
|
||||
}
|
||||
|
||||
protected boolean canEqual(Object other) {
|
||||
|
@ -25,10 +25,7 @@ final class Tokens {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s = tokenType().name() + "(" + value.valueType().name()
|
||||
+ ")";
|
||||
|
||||
return s + "='" + value().unwrapped() + "'";
|
||||
return "'" + value().unwrapped() + "' (" + value.valueType().name() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -61,7 +58,7 @@ final class Tokens {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NEWLINE@" + lineNumber;
|
||||
return "'\n'@" + lineNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,7 +99,7 @@ final class Tokens {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return tokenType().name() + "(" + value + ")";
|
||||
return "'" + value + "'";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -149,7 +146,11 @@ final class Tokens {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return tokenType().name() + "(" + value.toString() + ")";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Token t : value) {
|
||||
sb.append(t.toString());
|
||||
}
|
||||
return "'${" + sb.toString() + "}'";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -252,15 +253,15 @@ final class Tokens {
|
||||
}
|
||||
}
|
||||
|
||||
final static Token START = new Token(TokenType.START);
|
||||
final static Token END = new Token(TokenType.END);
|
||||
final static Token COMMA = new Token(TokenType.COMMA);
|
||||
final static Token EQUALS = new Token(TokenType.EQUALS);
|
||||
final static Token COLON = new Token(TokenType.COLON);
|
||||
final static Token OPEN_CURLY = new Token(TokenType.OPEN_CURLY);
|
||||
final static Token CLOSE_CURLY = new Token(TokenType.CLOSE_CURLY);
|
||||
final static Token OPEN_SQUARE = new Token(TokenType.OPEN_SQUARE);
|
||||
final static Token CLOSE_SQUARE = new Token(TokenType.CLOSE_SQUARE);
|
||||
final static Token START = new Token(TokenType.START, "start of file");
|
||||
final static Token END = new Token(TokenType.END, "end of file");
|
||||
final static Token COMMA = new Token(TokenType.COMMA, "','");
|
||||
final static Token EQUALS = new Token(TokenType.EQUALS, "'='");
|
||||
final static Token COLON = new Token(TokenType.COLON, "':'");
|
||||
final static Token OPEN_CURLY = new Token(TokenType.OPEN_CURLY, "'{'");
|
||||
final static Token CLOSE_CURLY = new Token(TokenType.CLOSE_CURLY, "'}'");
|
||||
final static Token OPEN_SQUARE = new Token(TokenType.OPEN_SQUARE, "'['");
|
||||
final static Token CLOSE_SQUARE = new Token(TokenType.CLOSE_SQUARE, "']'");
|
||||
|
||||
static Token newLine(int lineNumberJustEnded) {
|
||||
return new Line(lineNumberJustEnded);
|
||||
|
Loading…
Reference in New Issue
Block a user