mirror of
https://github.com/lightbend/config.git
synced 2025-03-29 05:20:42 +08:00
Merge pull request #82 from typesafehub/havocp-comment-affiliation
Associate comments after a value with that value, fixes #81
This commit is contained in:
commit
e76a54843e
@ -41,14 +41,26 @@ final class Parser {
|
|||||||
TokenWithComments(Token token, List<Token> comments) {
|
TokenWithComments(Token token, List<Token> comments) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.comments = comments;
|
this.comments = comments;
|
||||||
|
|
||||||
|
if (Tokens.isComment(token))
|
||||||
|
throw new ConfigException.BugOrBroken("tried to annotate a comment with a comment");
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenWithComments(Token token) {
|
TokenWithComments(Token token) {
|
||||||
this(token, Collections.<Token> emptyList());
|
this(token, Collections.<Token> emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TokenWithComments removeAll() {
|
||||||
|
if (comments.isEmpty())
|
||||||
|
return this;
|
||||||
|
else
|
||||||
|
return new TokenWithComments(token);
|
||||||
|
}
|
||||||
|
|
||||||
TokenWithComments prepend(List<Token> earlier) {
|
TokenWithComments prepend(List<Token> earlier) {
|
||||||
if (this.comments.isEmpty()) {
|
if (earlier.isEmpty()) {
|
||||||
|
return this;
|
||||||
|
} else if (this.comments.isEmpty()) {
|
||||||
return new TokenWithComments(token, earlier);
|
return new TokenWithComments(token, earlier);
|
||||||
} else {
|
} else {
|
||||||
List<Token> merged = new ArrayList<Token>();
|
List<Token> merged = new ArrayList<Token>();
|
||||||
@ -58,7 +70,18 @@ final class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleConfigOrigin setComments(SimpleConfigOrigin origin) {
|
TokenWithComments add(Token after) {
|
||||||
|
if (this.comments.isEmpty()) {
|
||||||
|
return new TokenWithComments(token, Collections.<Token> singletonList(after));
|
||||||
|
} else {
|
||||||
|
List<Token> merged = new ArrayList<Token>();
|
||||||
|
merged.addAll(comments);
|
||||||
|
merged.add(after);
|
||||||
|
return new TokenWithComments(token, merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleConfigOrigin prependComments(SimpleConfigOrigin origin) {
|
||||||
if (comments.isEmpty()) {
|
if (comments.isEmpty()) {
|
||||||
return origin;
|
return origin;
|
||||||
} else {
|
} else {
|
||||||
@ -66,7 +89,19 @@ final class Parser {
|
|||||||
for (Token c : comments) {
|
for (Token c : comments) {
|
||||||
newComments.add(Tokens.getCommentText(c));
|
newComments.add(Tokens.getCommentText(c));
|
||||||
}
|
}
|
||||||
return origin.setComments(newComments);
|
return origin.prependComments(newComments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleConfigOrigin appendComments(SimpleConfigOrigin origin) {
|
||||||
|
if (comments.isEmpty()) {
|
||||||
|
return origin;
|
||||||
|
} else {
|
||||||
|
List<String> newComments = new ArrayList<String>();
|
||||||
|
for (Token c : comments) {
|
||||||
|
newComments.add(Tokens.getCommentText(c));
|
||||||
|
}
|
||||||
|
return origin.appendComments(newComments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,12 +140,38 @@ final class Parser {
|
|||||||
this.equalsCount = 0;
|
this.equalsCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static private boolean attractsTrailingComments(Token token) {
|
||||||
|
// END can't have a trailing comment; START, OPEN_CURLY, and
|
||||||
|
// OPEN_SQUARE followed by a comment should behave as if the comment
|
||||||
|
// went with the following field or element. Associating a comment
|
||||||
|
// with a newline would mess up all the logic for comment tracking,
|
||||||
|
// so don't do that either.
|
||||||
|
if (Tokens.isNewline(token) || token == Tokens.START || token == Tokens.OPEN_CURLY
|
||||||
|
|| token == Tokens.OPEN_SQUARE || token == Tokens.END)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private boolean attractsLeadingComments(Token token) {
|
||||||
|
// a comment just before a close } generally doesn't go with the
|
||||||
|
// value before it, unless it's on the same line as that value
|
||||||
|
if (Tokens.isNewline(token) || token == Tokens.START || token == Tokens.CLOSE_CURLY
|
||||||
|
|| token == Tokens.CLOSE_SQUARE || token == Tokens.END)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void consolidateCommentBlock(Token commentToken) {
|
private void consolidateCommentBlock(Token commentToken) {
|
||||||
// a comment block "goes with" the following token
|
// a comment block "goes with" the following token
|
||||||
// unless it's separated from it by a blank line.
|
// unless it's separated from it by a blank line.
|
||||||
// we want to build a list of newline tokens followed
|
// we want to build a list of newline tokens followed
|
||||||
// by a non-newline non-comment token; with all comments
|
// by a non-newline non-comment token; with all comments
|
||||||
// associated with that final non-newline non-comment token.
|
// associated with that final non-newline non-comment token.
|
||||||
|
// a comment AFTER a token, without an intervening newline,
|
||||||
|
// also goes with that token, but isn't handled in this method,
|
||||||
|
// instead we handle it later by peeking ahead.
|
||||||
List<Token> newlines = new ArrayList<Token>();
|
List<Token> newlines = new ArrayList<Token>();
|
||||||
List<Token> comments = new ArrayList<Token>();
|
List<Token> comments = new ArrayList<Token>();
|
||||||
|
|
||||||
@ -128,6 +189,11 @@ final class Parser {
|
|||||||
comments.add(next);
|
comments.add(next);
|
||||||
} else {
|
} else {
|
||||||
// a non-newline non-comment token
|
// a non-newline non-comment token
|
||||||
|
|
||||||
|
// comments before a close brace or bracket just get dumped
|
||||||
|
if (!attractsLeadingComments(next))
|
||||||
|
comments.clear();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +212,7 @@ final class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenWithComments popToken() {
|
private TokenWithComments popTokenWithoutTrailingComment() {
|
||||||
if (buffer.isEmpty()) {
|
if (buffer.isEmpty()) {
|
||||||
Token t = tokens.next();
|
Token t = tokens.next();
|
||||||
if (Tokens.isComment(t)) {
|
if (Tokens.isComment(t)) {
|
||||||
@ -160,6 +226,35 @@ final class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TokenWithComments popToken() {
|
||||||
|
TokenWithComments withPrecedingComments = popTokenWithoutTrailingComment();
|
||||||
|
// handle a comment AFTER the other token,
|
||||||
|
// but before a newline. If the next token is not
|
||||||
|
// a comment, then any comment later on the line is irrelevant
|
||||||
|
// since it would end up going with that later token, not
|
||||||
|
// this token. Comments are supposed to be processed prior
|
||||||
|
// to adding stuff to the buffer, so they can only be found
|
||||||
|
// in "tokens" not in "buffer" in theory.
|
||||||
|
if (!attractsTrailingComments(withPrecedingComments.token)) {
|
||||||
|
return withPrecedingComments;
|
||||||
|
} else if (buffer.isEmpty()) {
|
||||||
|
Token after = tokens.next();
|
||||||
|
if (Tokens.isComment(after)) {
|
||||||
|
return withPrecedingComments.add(after);
|
||||||
|
} else {
|
||||||
|
buffer.push(new TokenWithComments(after));
|
||||||
|
return withPrecedingComments;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// comments are supposed to get attached to a token,
|
||||||
|
// not put back in the buffer. Assert this as an invariant.
|
||||||
|
if (Tokens.isComment(buffer.peek().token))
|
||||||
|
throw new ConfigException.BugOrBroken(
|
||||||
|
"comment token should not have been in buffer: " + buffer);
|
||||||
|
return withPrecedingComments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private TokenWithComments nextToken() {
|
private TokenWithComments nextToken() {
|
||||||
TokenWithComments withComments = null;
|
TokenWithComments withComments = null;
|
||||||
|
|
||||||
@ -192,6 +287,9 @@ final class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void putBack(TokenWithComments token) {
|
private void putBack(TokenWithComments token) {
|
||||||
|
if (Tokens.isComment(token.token))
|
||||||
|
throw new ConfigException.BugOrBroken(
|
||||||
|
"comment token should have been stripped before it was available to put back");
|
||||||
buffer.push(token);
|
buffer.push(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +314,19 @@ final class Parser {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AbstractConfigValue addAnyCommentsAfterAnyComma(AbstractConfigValue v) {
|
||||||
|
TokenWithComments t = nextToken(); // do NOT skip newlines, we only
|
||||||
|
// want same-line comments
|
||||||
|
if (t.token == Tokens.COMMA) {
|
||||||
|
// steal the comments from after the comma
|
||||||
|
putBack(t.removeAll());
|
||||||
|
return v.withOrigin(t.appendComments(v.origin()));
|
||||||
|
} else {
|
||||||
|
putBack(t);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// In arrays and objects, comma can be omitted
|
// In arrays and objects, comma can be omitted
|
||||||
// as long as there's at least one newline instead.
|
// as long as there's at least one newline instead.
|
||||||
// this skips any newlines in front of a comma,
|
// this skips any newlines in front of a comma,
|
||||||
@ -272,22 +383,14 @@ final class Parser {
|
|||||||
|
|
||||||
// create only if we have value tokens
|
// create only if we have value tokens
|
||||||
List<AbstractConfigValue> values = null;
|
List<AbstractConfigValue> values = null;
|
||||||
TokenWithComments firstValueWithComments = null;
|
|
||||||
// ignore a newline up front
|
// ignore a newline up front
|
||||||
TokenWithComments t = nextTokenIgnoringNewline();
|
TokenWithComments t = nextTokenIgnoringNewline();
|
||||||
while (true) {
|
while (true) {
|
||||||
AbstractConfigValue v = null;
|
AbstractConfigValue v = null;
|
||||||
if (Tokens.isValue(t.token)) {
|
if (Tokens.isValue(t.token) || Tokens.isUnquotedText(t.token)
|
||||||
// if we consolidateValueTokens() multiple times then
|
|| Tokens.isSubstitution(t.token) || t.token == Tokens.OPEN_CURLY
|
||||||
// this value could be a concatenation, object, array,
|
|| t.token == Tokens.OPEN_SQUARE) {
|
||||||
// or substitution already.
|
|
||||||
v = Tokens.getValue(t.token);
|
|
||||||
} else if (Tokens.isUnquotedText(t.token)) {
|
|
||||||
v = new ConfigString(t.token.origin(), Tokens.getUnquotedText(t.token));
|
|
||||||
} else if (Tokens.isSubstitution(t.token)) {
|
|
||||||
v = new ConfigReference(t.token.origin(),
|
|
||||||
tokenToSubstitutionExpression(t.token));
|
|
||||||
} else if (t.token == Tokens.OPEN_CURLY || t.token == Tokens.OPEN_SQUARE) {
|
|
||||||
// there may be newlines _within_ the objects and arrays
|
// there may be newlines _within_ the objects and arrays
|
||||||
v = parseValue(t);
|
v = parseValue(t);
|
||||||
} else {
|
} else {
|
||||||
@ -299,7 +402,6 @@ final class Parser {
|
|||||||
|
|
||||||
if (values == null) {
|
if (values == null) {
|
||||||
values = new ArrayList<AbstractConfigValue>();
|
values = new ArrayList<AbstractConfigValue>();
|
||||||
firstValueWithComments = t;
|
|
||||||
}
|
}
|
||||||
values.add(v);
|
values.add(v);
|
||||||
|
|
||||||
@ -313,11 +415,10 @@ final class Parser {
|
|||||||
|
|
||||||
AbstractConfigValue consolidated = ConfigConcatenation.concatenate(values);
|
AbstractConfigValue consolidated = ConfigConcatenation.concatenate(values);
|
||||||
|
|
||||||
putBack(new TokenWithComments(Tokens.newValue(consolidated),
|
putBack(new TokenWithComments(Tokens.newValue(consolidated)));
|
||||||
firstValueWithComments.comments));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigOrigin lineOrigin() {
|
private SimpleConfigOrigin lineOrigin() {
|
||||||
return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
|
return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,7 +504,14 @@ final class Parser {
|
|||||||
AbstractConfigValue v;
|
AbstractConfigValue v;
|
||||||
|
|
||||||
if (Tokens.isValue(t.token)) {
|
if (Tokens.isValue(t.token)) {
|
||||||
|
// if we consolidateValueTokens() multiple times then
|
||||||
|
// this value could be a concatenation, object, array,
|
||||||
|
// or substitution already.
|
||||||
v = Tokens.getValue(t.token);
|
v = Tokens.getValue(t.token);
|
||||||
|
} else if (Tokens.isUnquotedText(t.token)) {
|
||||||
|
v = new ConfigString(t.token.origin(), Tokens.getUnquotedText(t.token));
|
||||||
|
} else if (Tokens.isSubstitution(t.token)) {
|
||||||
|
v = new ConfigReference(t.token.origin(), tokenToSubstitutionExpression(t.token));
|
||||||
} else if (t.token == Tokens.OPEN_CURLY) {
|
} else if (t.token == Tokens.OPEN_CURLY) {
|
||||||
v = parseObject(true);
|
v = parseObject(true);
|
||||||
} else if (t.token == Tokens.OPEN_SQUARE) {
|
} else if (t.token == Tokens.OPEN_SQUARE) {
|
||||||
@ -413,7 +521,7 @@ final class Parser {
|
|||||||
"Expecting a value but got wrong token: " + t.token));
|
"Expecting a value but got wrong token: " + t.token));
|
||||||
}
|
}
|
||||||
|
|
||||||
v = v.withOrigin(t.setComments(v.origin()));
|
v = v.withOrigin(t.prependComments(v.origin()));
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@ -601,7 +709,7 @@ final class Parser {
|
|||||||
private AbstractConfigObject parseObject(boolean hadOpenCurly) {
|
private AbstractConfigObject parseObject(boolean hadOpenCurly) {
|
||||||
// invoked just after the OPEN_CURLY (or START, if !hadOpenCurly)
|
// invoked just after the OPEN_CURLY (or START, if !hadOpenCurly)
|
||||||
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
||||||
ConfigOrigin objectOrigin = lineOrigin();
|
SimpleConfigOrigin objectOrigin = lineOrigin();
|
||||||
boolean afterComma = false;
|
boolean afterComma = false;
|
||||||
Path lastPath = null;
|
Path lastPath = null;
|
||||||
boolean lastInsideEquals = false;
|
boolean lastInsideEquals = false;
|
||||||
@ -616,6 +724,9 @@ final class Parser {
|
|||||||
throw parseError(addQuoteSuggestion(t.toString(),
|
throw parseError(addQuoteSuggestion(t.toString(),
|
||||||
"unbalanced close brace '}' with no open brace"));
|
"unbalanced close brace '}' with no open brace"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
objectOrigin = t.appendComments(objectOrigin);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} else if (t.token == Tokens.END && !hadOpenCurly) {
|
} else if (t.token == Tokens.END && !hadOpenCurly) {
|
||||||
putBack(t);
|
putBack(t);
|
||||||
@ -652,8 +763,11 @@ final class Parser {
|
|||||||
|
|
||||||
consolidateValueTokens();
|
consolidateValueTokens();
|
||||||
valueToken = nextTokenIgnoringNewline();
|
valueToken = nextTokenIgnoringNewline();
|
||||||
|
// put comments from separator token on the value token
|
||||||
|
valueToken = valueToken.prepend(afterKey.comments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// comments from the key token go to the value token
|
||||||
newValue = parseValue(valueToken.prepend(keyToken.comments));
|
newValue = parseValue(valueToken.prepend(keyToken.comments));
|
||||||
|
|
||||||
if (afterKey.token == Tokens.PLUS_EQUALS) {
|
if (afterKey.token == Tokens.PLUS_EQUALS) {
|
||||||
@ -667,6 +781,8 @@ final class Parser {
|
|||||||
newValue = ConfigConcatenation.concatenate(concat);
|
newValue = ConfigConcatenation.concatenate(concat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newValue = addAnyCommentsAfterAnyComma(newValue);
|
||||||
|
|
||||||
lastPath = pathStack.pop();
|
lastPath = pathStack.pop();
|
||||||
if (insideEquals) {
|
if (insideEquals) {
|
||||||
equalsCount -= 1;
|
equalsCount -= 1;
|
||||||
@ -722,6 +838,9 @@ final class Parser {
|
|||||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||||
t.toString(), "unbalanced close brace '}' with no open brace"));
|
t.toString(), "unbalanced close brace '}' with no open brace"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
objectOrigin = t.appendComments(objectOrigin);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} else if (hadOpenCurly) {
|
} else if (hadOpenCurly) {
|
||||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||||
@ -743,7 +862,7 @@ final class Parser {
|
|||||||
|
|
||||||
private SimpleConfigList parseArray() {
|
private SimpleConfigList parseArray() {
|
||||||
// invoked just after the OPEN_SQUARE
|
// invoked just after the OPEN_SQUARE
|
||||||
ConfigOrigin arrayOrigin = lineOrigin();
|
SimpleConfigOrigin arrayOrigin = lineOrigin();
|
||||||
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
|
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
|
||||||
|
|
||||||
consolidateValueTokens();
|
consolidateValueTokens();
|
||||||
@ -752,11 +871,13 @@ final class Parser {
|
|||||||
|
|
||||||
// special-case the first element
|
// special-case the first element
|
||||||
if (t.token == Tokens.CLOSE_SQUARE) {
|
if (t.token == Tokens.CLOSE_SQUARE) {
|
||||||
return new SimpleConfigList(arrayOrigin,
|
return new SimpleConfigList(t.appendComments(arrayOrigin),
|
||||||
Collections.<AbstractConfigValue> emptyList());
|
Collections.<AbstractConfigValue> emptyList());
|
||||||
} else if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|
} else if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|
||||||
|| t.token == Tokens.OPEN_SQUARE) {
|
|| t.token == Tokens.OPEN_SQUARE) {
|
||||||
values.add(parseValue(t));
|
AbstractConfigValue v = parseValue(t);
|
||||||
|
v = addAnyCommentsAfterAnyComma(v);
|
||||||
|
values.add(v);
|
||||||
} else {
|
} else {
|
||||||
throw parseError(addKeyName("List should have ] or a first element after the open [, instead had token: "
|
throw parseError(addKeyName("List should have ] or a first element after the open [, instead had token: "
|
||||||
+ t
|
+ t
|
||||||
@ -773,7 +894,7 @@ final class Parser {
|
|||||||
} else {
|
} else {
|
||||||
t = nextTokenIgnoringNewline();
|
t = nextTokenIgnoringNewline();
|
||||||
if (t.token == Tokens.CLOSE_SQUARE) {
|
if (t.token == Tokens.CLOSE_SQUARE) {
|
||||||
return new SimpleConfigList(arrayOrigin, values);
|
return new SimpleConfigList(t.appendComments(arrayOrigin), values);
|
||||||
} else {
|
} else {
|
||||||
throw parseError(addKeyName("List should have ended with ] or had a comma, instead had token: "
|
throw parseError(addKeyName("List should have ended with ] or had a comma, instead had token: "
|
||||||
+ t
|
+ t
|
||||||
@ -789,7 +910,9 @@ final class Parser {
|
|||||||
t = nextTokenIgnoringNewline();
|
t = nextTokenIgnoringNewline();
|
||||||
if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|
if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|
||||||
|| t.token == Tokens.OPEN_SQUARE) {
|
|| t.token == Tokens.OPEN_SQUARE) {
|
||||||
values.add(parseValue(t));
|
AbstractConfigValue v = parseValue(t);
|
||||||
|
v = addAnyCommentsAfterAnyComma(v);
|
||||||
|
values.add(v);
|
||||||
} else if (flavor != ConfigSyntax.JSON && t.token == Tokens.CLOSE_SQUARE) {
|
} else if (flavor != ConfigSyntax.JSON && t.token == Tokens.CLOSE_SQUARE) {
|
||||||
// we allow one trailing comma
|
// we allow one trailing comma
|
||||||
putBack(t);
|
putBack(t);
|
||||||
|
@ -93,6 +93,34 @@ final class SimpleConfigOrigin implements ConfigOrigin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SimpleConfigOrigin prependComments(List<String> comments) {
|
||||||
|
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
|
||||||
|
return this;
|
||||||
|
} else if (this.commentsOrNull == null) {
|
||||||
|
return setComments(comments);
|
||||||
|
} else {
|
||||||
|
List<String> merged = new ArrayList<String>(comments.size()
|
||||||
|
+ this.commentsOrNull.size());
|
||||||
|
merged.addAll(comments);
|
||||||
|
merged.addAll(this.commentsOrNull);
|
||||||
|
return setComments(merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleConfigOrigin appendComments(List<String> comments) {
|
||||||
|
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
|
||||||
|
return this;
|
||||||
|
} else if (this.commentsOrNull == null) {
|
||||||
|
return setComments(comments);
|
||||||
|
} else {
|
||||||
|
List<String> merged = new ArrayList<String>(comments.size()
|
||||||
|
+ this.commentsOrNull.size());
|
||||||
|
merged.addAll(this.commentsOrNull);
|
||||||
|
merged.addAll(comments);
|
||||||
|
return setComments(merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String description() {
|
public String description() {
|
||||||
// not putting the URL in here for files and resources, because people
|
// not putting the URL in here for files and resources, because people
|
||||||
|
@ -367,6 +367,10 @@ class ConfParserTest extends TestUtils {
|
|||||||
Parseable.newReader(new StringReader("{}"), options).toString
|
Parseable.newReader(new StringReader("{}"), options).toString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def assertComments(comments: Seq[String], conf: Config) {
|
||||||
|
assertEquals(comments, conf.root().origin().comments().asScala.toSeq)
|
||||||
|
}
|
||||||
|
|
||||||
private def assertComments(comments: Seq[String], conf: Config, path: String) {
|
private def assertComments(comments: Seq[String], conf: Config, path: String) {
|
||||||
assertEquals(comments, conf.getValue(path).origin().comments().asScala.toSeq)
|
assertEquals(comments, conf.getValue(path).origin().comments().asScala.toSeq)
|
||||||
}
|
}
|
||||||
@ -377,17 +381,24 @@ class ConfParserTest extends TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
def trackCommentsForFields() {
|
def trackCommentsForSingleField() {
|
||||||
// comment in front of a field is used
|
// no comments
|
||||||
val conf1 = parseConfig("""
|
val conf0 = parseConfig("""
|
||||||
{ # Hello
|
{
|
||||||
foo=10 }
|
foo=10 }
|
||||||
""")
|
""")
|
||||||
assertComments(Seq(" Hello"), conf1, "foo")
|
assertComments(Seq(), conf0, "foo")
|
||||||
|
|
||||||
|
// comment in front of a field is used
|
||||||
|
val conf1 = parseConfig("""
|
||||||
|
{ # Before
|
||||||
|
foo=10 }
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" Before"), conf1, "foo")
|
||||||
|
|
||||||
// comment with a blank line after is dropped
|
// comment with a blank line after is dropped
|
||||||
val conf2 = parseConfig("""
|
val conf2 = parseConfig("""
|
||||||
{ # Hello
|
{ # BlankAfter
|
||||||
|
|
||||||
foo=10 }
|
foo=10 }
|
||||||
""")
|
""")
|
||||||
@ -395,19 +406,175 @@ class ConfParserTest extends TestUtils {
|
|||||||
|
|
||||||
// comment in front of a field is used with no root {}
|
// comment in front of a field is used with no root {}
|
||||||
val conf3 = parseConfig("""
|
val conf3 = parseConfig("""
|
||||||
# Hello
|
# BeforeNoBraces
|
||||||
foo=10
|
foo=10
|
||||||
""")
|
""")
|
||||||
assertComments(Seq(" Hello"), conf3, "foo")
|
assertComments(Seq(" BeforeNoBraces"), conf3, "foo")
|
||||||
|
|
||||||
// comment with a blank line after is dropped with no root {}
|
// comment with a blank line after is dropped with no root {}
|
||||||
val conf4 = parseConfig("""
|
val conf4 = parseConfig("""
|
||||||
# Hello
|
# BlankAfterNoBraces
|
||||||
|
|
||||||
foo=10
|
foo=10
|
||||||
""")
|
""")
|
||||||
assertComments(Seq(), conf4, "foo")
|
assertComments(Seq(), conf4, "foo")
|
||||||
|
|
||||||
|
// comment same line after field is used
|
||||||
|
val conf5 = parseConfig("""
|
||||||
|
{
|
||||||
|
foo=10 # SameLine
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" SameLine"), conf5, "foo")
|
||||||
|
|
||||||
|
// comment before field separator is used
|
||||||
|
val conf6 = parseConfig("""
|
||||||
|
{
|
||||||
|
foo # BeforeSep
|
||||||
|
=10
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" BeforeSep"), conf6, "foo")
|
||||||
|
|
||||||
|
// comment after field separator is used
|
||||||
|
val conf7 = parseConfig("""
|
||||||
|
{
|
||||||
|
foo= # AfterSep
|
||||||
|
10
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" AfterSep"), conf7, "foo")
|
||||||
|
|
||||||
|
// comment on next line is NOT used
|
||||||
|
val conf8 = parseConfig("""
|
||||||
|
{
|
||||||
|
foo=10
|
||||||
|
# NextLine
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
assertComments(Seq(), conf8, "foo")
|
||||||
|
|
||||||
|
// comment before field separator on new line
|
||||||
|
val conf9 = parseConfig("""
|
||||||
|
{
|
||||||
|
foo
|
||||||
|
# BeforeSepOwnLine
|
||||||
|
=10
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" BeforeSepOwnLine"), conf9, "foo")
|
||||||
|
|
||||||
|
// comment after field separator on its own line
|
||||||
|
val conf10 = parseConfig("""
|
||||||
|
{
|
||||||
|
foo=
|
||||||
|
# AfterSepOwnLine
|
||||||
|
10
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" AfterSepOwnLine"), conf10, "foo")
|
||||||
|
|
||||||
|
// comments comments everywhere
|
||||||
|
val conf11 = parseConfig("""
|
||||||
|
{# Before
|
||||||
|
foo
|
||||||
|
# BeforeSep
|
||||||
|
= # AfterSepSameLine
|
||||||
|
# AfterSepNextLine
|
||||||
|
10 # AfterValue
|
||||||
|
# AfterValueNewLine (should NOT be used)
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" Before", " BeforeSep", " AfterSepSameLine", " AfterSepNextLine", " AfterValue"), conf11, "foo")
|
||||||
|
|
||||||
|
// empty object
|
||||||
|
val conf12 = parseConfig("""# BeforeEmpty
|
||||||
|
{} #AfterEmpty
|
||||||
|
# NewLine
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" BeforeEmpty", "AfterEmpty"), conf12)
|
||||||
|
|
||||||
|
// empty array
|
||||||
|
val conf13 = parseConfig("""
|
||||||
|
foo=
|
||||||
|
# BeforeEmptyArray
|
||||||
|
[] #AfterEmptyArray
|
||||||
|
# NewLine
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" BeforeEmptyArray", "AfterEmptyArray"), conf13, "foo")
|
||||||
|
|
||||||
|
// array element
|
||||||
|
val conf14 = parseConfig("""
|
||||||
|
foo=[
|
||||||
|
# BeforeElement
|
||||||
|
10 # AfterElement
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" BeforeElement", " AfterElement"), conf14, "foo", 0)
|
||||||
|
|
||||||
|
// field with comma after it
|
||||||
|
val conf15 = parseConfig("""
|
||||||
|
foo=10, # AfterCommaField
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" AfterCommaField"), conf15, "foo")
|
||||||
|
|
||||||
|
// element with comma after it
|
||||||
|
val conf16 = parseConfig("""
|
||||||
|
foo=[10, # AfterCommaElement
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" AfterCommaElement"), conf16, "foo", 0)
|
||||||
|
|
||||||
|
// field with comma after it but comment isn't on the field's line, so not used
|
||||||
|
val conf17 = parseConfig("""
|
||||||
|
foo=10
|
||||||
|
, # AfterCommaFieldNotUsed
|
||||||
|
""")
|
||||||
|
assertComments(Seq(), conf17, "foo")
|
||||||
|
|
||||||
|
// element with comma after it but comment isn't on the field's line, so not used
|
||||||
|
val conf18 = parseConfig("""
|
||||||
|
foo=[10
|
||||||
|
, # AfterCommaElementNotUsed
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
assertComments(Seq(), conf18, "foo", 0)
|
||||||
|
|
||||||
|
// comment on new line, before comma, should not be used
|
||||||
|
val conf19 = parseConfig("""
|
||||||
|
foo=10
|
||||||
|
# BeforeCommaFieldNotUsed
|
||||||
|
,
|
||||||
|
""")
|
||||||
|
assertComments(Seq(), conf19, "foo")
|
||||||
|
|
||||||
|
// comment on new line, before comma, should not be used
|
||||||
|
val conf20 = parseConfig("""
|
||||||
|
foo=[10
|
||||||
|
# BeforeCommaElementNotUsed
|
||||||
|
,
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
assertComments(Seq(), conf20, "foo", 0)
|
||||||
|
|
||||||
|
// comment on same line before comma
|
||||||
|
val conf21 = parseConfig("""
|
||||||
|
foo=10 # BeforeCommaFieldSameLine
|
||||||
|
,
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" BeforeCommaFieldSameLine"), conf21, "foo")
|
||||||
|
|
||||||
|
// comment on same line before comma
|
||||||
|
val conf22 = parseConfig("""
|
||||||
|
foo=[10 # BeforeCommaElementSameLine
|
||||||
|
,
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
assertComments(Seq(" BeforeCommaElementSameLine"), conf22, "foo", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def trackCommentsForMultipleFields() {
|
||||||
// nested objects
|
// nested objects
|
||||||
val conf5 = parseConfig("""
|
val conf5 = parseConfig("""
|
||||||
# Outside
|
# Outside
|
||||||
@ -418,28 +585,28 @@ class ConfParserTest extends TestUtils {
|
|||||||
# two lines
|
# two lines
|
||||||
baz {
|
baz {
|
||||||
# Inner
|
# Inner
|
||||||
foo=10 # should be ignored
|
foo=10 # AfterInner
|
||||||
# This should be ignored too
|
# This should be ignored
|
||||||
} ## not used
|
} # AfterMiddle
|
||||||
# ignored
|
# ignored
|
||||||
}
|
} # AfterOutside
|
||||||
# ignored!
|
# ignored!
|
||||||
""")
|
""")
|
||||||
assertComments(Seq(" Inner"), conf5, "bar.baz.foo")
|
assertComments(Seq(" Inner", " AfterInner"), conf5, "bar.baz.foo")
|
||||||
assertComments(Seq(" Middle", " two lines"), conf5, "bar.baz")
|
assertComments(Seq(" Middle", " two lines", " AfterMiddle"), conf5, "bar.baz")
|
||||||
assertComments(Seq(" Outside"), conf5, "bar")
|
assertComments(Seq(" Outside", " AfterOutside"), conf5, "bar")
|
||||||
|
|
||||||
// multiple fields
|
// multiple fields
|
||||||
val conf6 = parseConfig("""{
|
val conf6 = parseConfig("""{
|
||||||
# this is not with a field
|
# this is not with a field
|
||||||
|
|
||||||
# this is field A
|
# this is field A
|
||||||
a : 10
|
a : 10,
|
||||||
# this is field B
|
# this is field B
|
||||||
b : 12 # goes with field C
|
b : 12 # goes with field B which has no comma
|
||||||
# this is field C
|
# this is field C
|
||||||
c : 14,
|
c : 14, # goes with field C after comma
|
||||||
|
# not used
|
||||||
# this is not used
|
# this is not used
|
||||||
# nor is this
|
# nor is this
|
||||||
# multi-line block
|
# multi-line block
|
||||||
@ -451,25 +618,27 @@ class ConfParserTest extends TestUtils {
|
|||||||
# this is after the fields
|
# this is after the fields
|
||||||
}""")
|
}""")
|
||||||
assertComments(Seq(" this is field A"), conf6, "a")
|
assertComments(Seq(" this is field A"), conf6, "a")
|
||||||
assertComments(Seq(" this is field B"), conf6, "b")
|
assertComments(Seq(" this is field B", " goes with field B which has no comma"), conf6, "b")
|
||||||
assertComments(Seq(" goes with field C", " this is field C"), conf6, "c")
|
assertComments(Seq(" this is field C", " goes with field C after comma"), conf6, "c")
|
||||||
assertComments(Seq(" this is with field D", " this is with field D also"), conf6, "d")
|
assertComments(Seq(" this is with field D", " this is with field D also"), conf6, "d")
|
||||||
|
|
||||||
// array
|
// array
|
||||||
val conf7 = parseConfig("""
|
val conf7 = parseConfig("""
|
||||||
|
# before entire array
|
||||||
array = [
|
array = [
|
||||||
# goes with 0
|
# goes with 0
|
||||||
0,
|
0,
|
||||||
# goes with 1
|
# goes with 1
|
||||||
1, # with 2
|
1, # with 1 after comma
|
||||||
# goes with 2
|
# goes with 2
|
||||||
2
|
2 # no comma after 2
|
||||||
# not with anything
|
# not with anything
|
||||||
]
|
] # after entire array
|
||||||
""")
|
""")
|
||||||
assertComments(Seq(" goes with 0"), conf7, "array", 0)
|
assertComments(Seq(" goes with 0"), conf7, "array", 0)
|
||||||
assertComments(Seq(" goes with 1"), conf7, "array", 1)
|
assertComments(Seq(" goes with 1", " with 1 after comma"), conf7, "array", 1)
|
||||||
assertComments(Seq(" with 2", " goes with 2"), conf7, "array", 2)
|
assertComments(Seq(" goes with 2", " no comma after 2"), conf7, "array", 2)
|
||||||
|
assertComments(Seq(" before entire array", " after entire array"), conf7, "array")
|
||||||
|
|
||||||
// properties-like syntax
|
// properties-like syntax
|
||||||
val conf8 = parseConfig("""
|
val conf8 = parseConfig("""
|
||||||
@ -484,6 +653,7 @@ class ConfParserTest extends TestUtils {
|
|||||||
# a.b comment
|
# a.b comment
|
||||||
a.b = 14
|
a.b = 14
|
||||||
a.c = 15
|
a.c = 15
|
||||||
|
a.d = 16 # a.d comment
|
||||||
# ignored comment
|
# ignored comment
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@ -492,6 +662,7 @@ class ConfParserTest extends TestUtils {
|
|||||||
assertComments(Seq(" x.a comment"), conf8, "x.a")
|
assertComments(Seq(" x.a comment"), conf8, "x.a")
|
||||||
assertComments(Seq(" a.b comment"), conf8, "a.b")
|
assertComments(Seq(" a.b comment"), conf8, "a.b")
|
||||||
assertComments(Seq(), conf8, "a.c")
|
assertComments(Seq(), conf8, "a.c")
|
||||||
|
assertComments(Seq(" a.d comment"), conf8, "a.d")
|
||||||
// here we're concerned that comments apply only to leaf
|
// here we're concerned that comments apply only to leaf
|
||||||
// nodes, not to parent objects.
|
// nodes, not to parent objects.
|
||||||
assertComments(Seq(), conf8, "x")
|
assertComments(Seq(), conf8, "x")
|
||||||
|
Loading…
Reference in New Issue
Block a user