Skip to content

Commit 156cf92

Browse files
authored
Graceful handling of YTT in YAML (#1706)
* Gracefully handle YTT in Yaml Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * WIP: fixing unit tests Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * Fix unit tests Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * Adjust unit tests Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> --------- Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
1 parent a91ca06 commit 156cf92

File tree

4 files changed

+63
-14
lines changed

4 files changed

+63
-14
lines changed

headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/YamlParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2016-2023 Pivotal, Inc.
2+
* Copyright (c) 2016-2025 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -37,6 +37,7 @@ public YamlFileAST getAST(IDocument doc) throws Exception {
3737
CharSequenceReader reader = new CharSequenceReader();
3838
reader.setInput(atTokenTransformHack(doc.get()));
3939
LoaderOptions loaderOpts = new LoaderOptions();
40+
loaderOpts.setProcessComments(true);
4041
loaderOpts.setMaxAliasesForCollections(1000);
4142
Iterable<Node> nodes = new Yaml(new SafeConstructor(loaderOpts), new Representer(new DumperOptions()), new DumperOptions(), loaderOpts).composeAll(reader);
4243
return new YamlFileAST(doc, ImmutableList.copyOf(nodes));

headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2016-2017 Pivotal, Inc.
2+
* Copyright (c) 2016-2025 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -100,21 +100,23 @@ public void reconcile(YamlFileAST ast) {
100100
slowDelayedConstraints.clear();
101101
try {
102102
List<Node> nodes = ast.getNodes();
103+
boolean emptyDoc = isEmptyDoc(ast);
103104
IntegerRange expectedDocs = schema.expectedNumberOfDocuments();
104-
if (!expectedDocs.isInRange(nodes.size())) {
105+
int numberOfDocs = emptyDoc ? 0 : nodes.size();
106+
if (!expectedDocs.isInRange(numberOfDocs)) {
105107
//wrong number of documents in the file. Figure out a good error message.
106-
if (nodes.isEmpty()) {
108+
if (emptyDoc) {
107109
problem(allOf(ast.getDocument()), "'"+schema.getName()+"' must have at least some Yaml content");
108-
} else if (expectedDocs.isTooLarge(nodes.size())) {
110+
} else if (expectedDocs.isTooLarge(numberOfDocs)) {
109111
int upperBound = expectedDocs.getUpperBound();
110112
Node extraNode = nodes.get(upperBound);
111113
problem(dashesAtStartOf(ast, extraNode), "'"+schema.getName()+"' should not have more than "+upperBound+" Yaml Documents");
112-
} else if (expectedDocs.isTooSmall(nodes.size())) {
114+
} else if (expectedDocs.isTooSmall(numberOfDocs)) {
113115
int lowerBound = expectedDocs.getLowerBound();
114116
problem(endOf(ast.getDocument()), "'"+schema.getName()+"' should have at least "+lowerBound+" Yaml Documents");
115117
}
116118
}
117-
if (nodes!=null && !nodes.isEmpty()) {
119+
if (!emptyDoc && nodes!=null && !nodes.isEmpty()) {
118120
for (int i = 0; i < nodes.size(); i++) {
119121
Node node = nodes.get(i);
120122
reconcile(ast, new YamlPath(YamlPathSegment.valueAt(i)), /*parent*/null, node, schema.getTopLevelType());
@@ -127,6 +129,21 @@ public void reconcile(YamlFileAST ast) {
127129
verifyDelayedConstraints();
128130
}
129131
}
132+
133+
private static boolean isEmptyDoc(YamlFileAST ast) {
134+
List<Node> nodes = ast.getNodes();
135+
if (nodes != null && !nodes.isEmpty()) {
136+
if (nodes.size() == 1) {
137+
Node n = nodes.get(0);
138+
if (n instanceof MappingNode) {
139+
MappingNode mn = (MappingNode) n;
140+
return mn.getValue().isEmpty();
141+
}
142+
}
143+
return false;
144+
}
145+
return true;
146+
}
130147

131148
private DocumentRegion dashesAtStartOf(YamlFileAST ast, Node node) {
132149
try {
@@ -240,12 +257,31 @@ private void parse(YamlFileAST ast, Node node, YType type, ValueParser parser) {
240257
parser.parse(value);
241258
}
242259
} catch (Exception e) {
243-
ProblemType problemType = getProblemType(e);
244-
DocumentRegion region = getRegion(e, ast.getDocument(), node);
245-
String msg = getMessage(e);
246-
valueParseError(type, region, msg, problemType, getValueReplacement(e));
260+
// check for YTT
261+
if (!isYttUsed(node)) {
262+
ProblemType problemType = getProblemType(e);
263+
DocumentRegion region = getRegion(e, ast.getDocument(), node);
264+
String msg = getMessage(e);
265+
valueParseError(type, region, msg, problemType, getValueReplacement(e));
266+
}
247267
}
248268
}
269+
270+
private static boolean isYttUsed(Node node) {
271+
if (node.getBlockComments() != null
272+
&& node.getBlockComments().stream().anyMatch(cl -> isYttComment(cl.getValue()))) {
273+
return true;
274+
}
275+
if (node.getInLineComments() != null
276+
&& node.getInLineComments().stream().anyMatch(cl -> isYttComment(cl.getValue()))) {
277+
return true;
278+
}
279+
return false;
280+
}
281+
282+
private static boolean isYttComment(String s) {
283+
return s.startsWith("@ ");
284+
}
249285

250286
protected ReplacementQuickfix getValueReplacement(Exception _e) {
251287
if (_e instanceof ReconcileException) {

headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlHierarchicalSymbolHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ public DocumentSymbol createSymbol(YamlFileAST currentAst, Node node, YType type
7676
IDocument doc = currentAst.getDocument();
7777
if (namePath!=null) {
7878
Node nameNode = namePath.traverseNode(node);
79-
if (nameNode!=null) {
79+
// VSCode throws for DocumentSymbols having null or empty name hence do not create the symbol
80+
if (nameNode!=null && !NodeUtil.asScalar(nameNode).isEmpty()) {
8081
DocumentRegion nodeRegion = NodeUtil.region(doc, node);
8182
DocumentRegion nameRegion = NodeUtil.region(doc, nameNode);
8283
if (!nodeRegion.contains(nameRegion)) {

headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ void reconcileResourceTypeType() throws Exception {
371371
" type: # <- bad\n"
372372
);
373373
editor.assertProblems(
374-
"^ # <- bad|cannot be blank"
374+
"# <- bad\n^^|cannot be blank"
375375
);
376376

377377
editor = harness.newEditor(
@@ -384,6 +384,17 @@ void reconcileResourceTypeType() throws Exception {
384384
);
385385
}
386386

387+
@Test
388+
void reconcileYttResourceType() throws Exception {
389+
Editor editor;
390+
editor = harness.newEditor(
391+
"resource_types:\n" +
392+
"- name: s3-multi\n" +
393+
" type: #@ resource.type\n"
394+
);
395+
editor.assertProblems();
396+
}
397+
387398
@Test
388399
void reconcileDisplayType() throws Exception {
389400
Editor editor;
@@ -393,7 +404,7 @@ void reconcileDisplayType() throws Exception {
393404
);
394405

395406
editor.assertProblems(
396-
"^ # <- bad|String should not be empty"
407+
"# <- bad\n^^|String should not be empty"
397408
);
398409
}
399410

0 commit comments

Comments
 (0)