Skip to content

Commit 2984f59

Browse files
committed
feat(adapter-yaml-1-2): add full support for empty nodes
Refs #916
1 parent bfab940 commit 2984f59

19 files changed

+1142
-64
lines changed

packages/apidom-parser-adapter-yaml-1-2/src/syntactic-analysis/visitors/CstVisitor.ts

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,7 @@ const CstVisitor = stampit({
126126
const keyNode = getFieldFromNode('key', node);
127127

128128
// keyNode was not explicitly provided; tag and anchor are missing too
129-
if (keyNode === null) {
130-
return true;
131-
}
132-
133-
// keyNode was not explicitly provided; tag or anchor are provided though
134-
// @ts-ignore
135-
return !keyNode.children.some(
136-
(n: SyntaxNode) => isScalar(n) || isSequence(n) || isMapping(n),
137-
);
129+
return keyNode === null;
138130
};
139131

140132
const hasKeyValuePairEmptyValue = (node: SyntaxNode) => {
@@ -145,15 +137,7 @@ const CstVisitor = stampit({
145137
const valueNode = getFieldFromNode('value', node);
146138

147139
// valueNode was not explicitly provided; tag and anchor are missing too
148-
if (valueNode === null) {
149-
return true;
150-
}
151-
152-
// valueNode was not explicitly provided; tag or anchor are provided though
153-
// @ts-ignore
154-
return !valueNode.children.some(
155-
(n: SyntaxNode) => isScalar(n) || isSequence(n) || isMapping(n),
156-
);
140+
return valueNode === null;
157141
};
158142

159143
const createKeyValuePairEmptyKey = (node: SyntaxNode) => {
@@ -344,7 +328,29 @@ const CstVisitor = stampit({
344328

345329
this.flow_node = {
346330
enter(node: SyntaxNode) {
347-
return node.children;
331+
const [kindCandidate] = node.children.slice(-1);
332+
333+
// kind node is present in flow node
334+
if (isScalar(kindCandidate) || isMapping(kindCandidate) || isSequence(kindCandidate)) {
335+
return node.children;
336+
}
337+
338+
// kind node not present in flow node, creating empty node
339+
const emptyPoint = Point({
340+
row: kindCandidate.endPosition.row,
341+
column: kindCandidate.endPosition.column,
342+
char: kindCandidate.endIndex,
343+
});
344+
const emptyScalarNode = YamlScalar({
345+
content: '',
346+
anchor: kindNodeToYamlAnchor(kindCandidate),
347+
tag: kindNodeToYamlTag(kindCandidate),
348+
position: Position({ start: emptyPoint, end: emptyPoint }),
349+
styleGroup: YamlStyleGroup.Flow,
350+
style: YamlStyle.Plain,
351+
});
352+
353+
return [...node.children, emptyScalarNode];
348354
},
349355
};
350356

@@ -386,7 +392,7 @@ const CstVisitor = stampit({
386392

387393
if (hasKeyValuePairEmptyKey(node)) {
388394
const keyNode = createKeyValuePairEmptyKey(node);
389-
children.push(keyNode);
395+
children.unshift(keyNode);
390396
}
391397
if (hasKeyValuePairEmptyValue(node)) {
392398
const valueNode = createKeyValuePairEmptyValue(node);
@@ -428,7 +434,7 @@ const CstVisitor = stampit({
428434

429435
if (hasKeyValuePairEmptyKey(node)) {
430436
const keyNode = createKeyValuePairEmptyKey(node);
431-
children.push(keyNode);
437+
children.unshift(keyNode);
432438
}
433439
if (hasKeyValuePairEmptyValue(node)) {
434440
const valueNode = createKeyValuePairEmptyValue(node);
@@ -470,7 +476,30 @@ const CstVisitor = stampit({
470476

471477
this.block_sequence_item = {
472478
enter(node: SyntaxNode) {
473-
return node.children;
479+
// flow or block node present; first node is always `-` literal
480+
if (node.children.length > 1) {
481+
return node.children;
482+
}
483+
484+
// create empty node
485+
const emptyPoint = Point({
486+
row: node.endPosition.row,
487+
column: node.endPosition.column,
488+
char: node.endIndex,
489+
});
490+
const emptyScalarNode = YamlScalar({
491+
content: '',
492+
anchor: null,
493+
tag: YamlTag({
494+
explicitName: '?',
495+
kind: YamlNodeKind.Scalar,
496+
}),
497+
position: Position({ start: emptyPoint, end: emptyPoint }),
498+
styleGroup: YamlStyleGroup.Flow,
499+
style: YamlStyle.Plain,
500+
});
501+
502+
return [emptyScalarNode];
474503
},
475504
};
476505

packages/apidom-parser-adapter-yaml-1-2/test/adapter-browser.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import fs from 'fs';
22
import path from 'path';
33
import { assert, expect } from 'chai';
4-
import dedent from 'dedent';
5-
import {
6-
isObjectElement,
7-
isParseResultElement,
8-
sexprs,
9-
SourceMapElement,
10-
} from '@swagger-api/apidom-core';
4+
import { isObjectElement, isParseResultElement, sexprs } from '@swagger-api/apidom-core';
115

126
import * as adapter from '../src/adapter-browser';
137

@@ -51,19 +45,4 @@ describe('adapter-browser', function () {
5145
assert.isTrue(parseResult.isEmpty);
5246
});
5347
});
54-
55-
context('given YAML with empty node', function () {
56-
specify('should generate source maps', async function () {
57-
const yamlSource = dedent`
58-
mapping:
59-
sub-mapping:
60-
`;
61-
62-
const { result } = await adapter.parse(yamlSource, { sourceMap: true });
63-
// @ts-ignore
64-
const subMappingValue = result.get('mapping').get('sub-mapping');
65-
66-
assert.instanceOf(subMappingValue.meta.get('sourceMap'), SourceMapElement);
67-
});
68-
});
6948
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`adapter-node should parse 1`] = `
4+
(ParseResultElement
5+
(CommentElement)
6+
(ObjectElement
7+
(MemberElement
8+
(StringElement)
9+
(StringElement))
10+
(MemberElement
11+
(StringElement)
12+
(StringElement))))
13+
`;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { assert } from 'chai';
2+
import { includesClasses, SourceMapElement } from '@swagger-api/apidom-core';
3+
4+
import * as adapter from '../../../../../src/adapter-node';
5+
6+
const setupEmptyElement = async () => {
7+
const yamlSource = '!str &anchor : value';
8+
const { result } = await adapter.parse(yamlSource, { sourceMap: true });
9+
// @ts-ignore
10+
return result.getMember('').key;
11+
};
12+
13+
describe('given empty node with tag and anchor as block mapping key', function () {
14+
it('should create empty element', async function () {
15+
const emptyElement = await setupEmptyElement();
16+
17+
assert.isTrue(includesClasses(['yaml-e-node', 'yaml-e-scalar'], emptyElement));
18+
});
19+
20+
it('should generate source maps', async function () {
21+
const emptyElement = await setupEmptyElement();
22+
const sourceMapElement = emptyElement.meta.get('sourceMap');
23+
24+
assert.instanceOf(sourceMapElement, SourceMapElement);
25+
});
26+
27+
it('should generate proper source map start position', async function () {
28+
const emptyElement = await setupEmptyElement();
29+
const sourceMapElement = emptyElement.meta.get('sourceMap');
30+
const [row, column, char] = [
31+
sourceMapElement.positionStart.get(0).toValue(),
32+
sourceMapElement.positionStart.get(1).toValue(),
33+
sourceMapElement.positionStart.get(2).toValue(),
34+
];
35+
36+
assert.strictEqual(row, 0);
37+
assert.strictEqual(column, 12);
38+
assert.strictEqual(char, 12);
39+
});
40+
41+
it('should generate proper source map end position', async function () {
42+
const emptyElement = await setupEmptyElement();
43+
const sourceMapElement = emptyElement.meta.get('sourceMap');
44+
const [row, column, char] = [
45+
sourceMapElement.positionEnd.get(0).toValue(),
46+
sourceMapElement.positionEnd.get(1).toValue(),
47+
sourceMapElement.positionEnd.get(2).toValue(),
48+
];
49+
50+
assert.strictEqual(row, 0);
51+
assert.strictEqual(column, 12);
52+
assert.strictEqual(char, 12);
53+
});
54+
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { assert } from 'chai';
2+
import dedent from 'dedent';
3+
import { includesClasses, SourceMapElement } from '@swagger-api/apidom-core';
4+
5+
import * as adapter from '../../../../../src/adapter-node';
6+
7+
const setupMemberElement = async (): Promise<any> => {
8+
const yamlSource = dedent`
9+
key: value
10+
?
11+
`;
12+
const { result } = await adapter.parse(yamlSource, { sourceMap: true });
13+
// @ts-ignore
14+
return result.content[1];
15+
};
16+
const setupEmptyKeyElement = async () => (await setupMemberElement()).key;
17+
const setupEmptyValueElement = async () => (await setupMemberElement()).value;
18+
19+
describe('given block mapping pair specified as optional “?” mapping key indicator', function () {
20+
it('should create empty key element', async function () {
21+
const emptyElement = await setupEmptyKeyElement();
22+
23+
assert.isTrue(includesClasses(['yaml-e-node', 'yaml-e-scalar'], emptyElement));
24+
});
25+
26+
it('should generate source maps for empty key', async function () {
27+
const emptyElement = await setupEmptyKeyElement();
28+
const sourceMapElement = emptyElement.meta.get('sourceMap');
29+
30+
assert.instanceOf(sourceMapElement, SourceMapElement);
31+
});
32+
33+
it('should generate proper source map start position for empty key', async function () {
34+
const emptyElement = await setupEmptyKeyElement();
35+
const sourceMapElement = emptyElement.meta.get('sourceMap');
36+
const [row, column, char] = [
37+
sourceMapElement.positionStart.get(0).toValue(),
38+
sourceMapElement.positionStart.get(1).toValue(),
39+
sourceMapElement.positionStart.get(2).toValue(),
40+
];
41+
42+
assert.strictEqual(row, 1);
43+
assert.strictEqual(column, 0);
44+
assert.strictEqual(char, 11);
45+
});
46+
47+
it('should generate proper source map end position for empty key', async function () {
48+
const emptyElement = await setupEmptyKeyElement();
49+
const sourceMapElement = emptyElement.meta.get('sourceMap');
50+
const [row, column, char] = [
51+
sourceMapElement.positionEnd.get(0).toValue(),
52+
sourceMapElement.positionEnd.get(1).toValue(),
53+
sourceMapElement.positionEnd.get(2).toValue(),
54+
];
55+
56+
assert.strictEqual(row, 1);
57+
assert.strictEqual(column, 0);
58+
assert.strictEqual(char, 11);
59+
});
60+
61+
it('should create empty value element', async function () {
62+
const emptyElement = await setupEmptyValueElement();
63+
64+
assert.isTrue(includesClasses(['yaml-e-node', 'yaml-e-scalar'], emptyElement));
65+
});
66+
67+
it('should generate source maps for empty value', async function () {
68+
const emptyElement = await setupEmptyValueElement();
69+
const sourceMapElement = emptyElement.meta.get('sourceMap');
70+
71+
assert.instanceOf(sourceMapElement, SourceMapElement);
72+
});
73+
74+
it('should generate proper source map start position for empty value', async function () {
75+
const emptyElement = await setupEmptyValueElement();
76+
const sourceMapElement = emptyElement.meta.get('sourceMap');
77+
const [row, column, char] = [
78+
sourceMapElement.positionStart.get(0).toValue(),
79+
sourceMapElement.positionStart.get(1).toValue(),
80+
sourceMapElement.positionStart.get(2).toValue(),
81+
];
82+
83+
assert.strictEqual(row, 1);
84+
assert.strictEqual(column, 1);
85+
assert.strictEqual(char, 12);
86+
});
87+
88+
it('should generate proper source map end position for empty value', async function () {
89+
const emptyElement = await setupEmptyValueElement();
90+
const sourceMapElement = emptyElement.meta.get('sourceMap');
91+
const [row, column, char] = [
92+
sourceMapElement.positionEnd.get(0).toValue(),
93+
sourceMapElement.positionEnd.get(1).toValue(),
94+
sourceMapElement.positionEnd.get(2).toValue(),
95+
];
96+
97+
assert.strictEqual(row, 1);
98+
assert.strictEqual(column, 1);
99+
assert.strictEqual(char, 12);
100+
});
101+
});

0 commit comments

Comments
 (0)