ut-tools
Version:
Build and Release management automation package.
456 lines • 18.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ordered_object_literal_1 = require("@stoplight/ordered-object-literal");
const types_1 = require("@stoplight/types");
const yaml_ast_parser_1 = require("@stoplight/yaml-ast-parser");
const buildJsonPath_1 = require("./buildJsonPath");
const dereferenceAnchor_1 = require("./dereferenceAnchor");
const lineForPosition_1 = require("./lineForPosition");
const types_2 = require("./types");
const utils_1 = require("./utils");
exports.parseWithPointers = (value, options) => {
const lineMap = computeLineMap(value);
const ast = yaml_ast_parser_1.load(value, Object.assign({}, options, { ignoreDuplicateKeys: true }));
const parsed = {
ast,
lineMap,
data: undefined,
diagnostics: [],
metadata: options,
comments: {},
};
if (!ast)
return parsed;
const normalizedOptions = normalizeOptions(options);
const comments = new Comments(parsed.comments, Comments.mapComments(normalizedOptions.attachComments && ast.comments ? ast.comments : [], lineMap), ast, lineMap, '#');
const ctx = {
lineMap,
diagnostics: parsed.diagnostics,
};
parsed.data = walkAST(ctx, ast, comments, normalizedOptions);
if (ast.errors) {
parsed.diagnostics.push(...transformErrors(ast.errors, lineMap));
}
if (parsed.diagnostics.length > 0) {
parsed.diagnostics.sort((itemA, itemB) => itemA.range.start.line - itemB.range.start.line);
}
if (Array.isArray(parsed.ast.errors)) {
parsed.ast.errors.length = 0;
}
return parsed;
};
const TILDE_REGEXP = /~/g;
const SLASH_REGEXP = /\//g;
function encodeSegment(input) {
return input.replace(TILDE_REGEXP, '~0').replace(SLASH_REGEXP, '~1');
}
const walkAST = (ctx, node, comments, options) => {
if (node) {
switch (node.kind) {
case types_2.Kind.MAP: {
const mapComments = comments.enter(node);
const { lineMap, diagnostics } = ctx;
const { preserveKeyOrder, ignoreDuplicateKeys, json, mergeKeys } = options;
const container = createMapContainer(preserveKeyOrder);
const seenKeys = [];
const handleMergeKeys = mergeKeys;
const yamlMode = !json;
const handleDuplicates = !ignoreDuplicateKeys;
for (const mapping of node.mappings) {
if (!validateMappingKey(mapping, lineMap, diagnostics, yamlMode))
continue;
const key = String(getScalarValue(mapping.key));
const mappingComments = mapComments.enter(mapping, encodeSegment(key));
if ((yamlMode || handleDuplicates) && (!handleMergeKeys || key !== "<<")) {
if (seenKeys.includes(key)) {
if (yamlMode) {
throw new Error('Duplicate YAML mapping key encountered');
}
if (handleDuplicates) {
diagnostics.push(createYAMLException(mapping.key, lineMap, 'duplicate key'));
}
}
else {
seenKeys.push(key);
}
}
if (handleMergeKeys && key === "<<") {
const reduced = reduceMergeKeys(walkAST(ctx, mapping.value, mappingComments, options), preserveKeyOrder);
Object.assign(container, reduced);
}
else {
container[key] = walkAST(ctx, mapping.value, mappingComments, options);
if (preserveKeyOrder) {
pushKey(container, key);
}
}
mappingComments.attachComments();
}
mapComments.attachComments();
return container;
}
case types_2.Kind.SEQ: {
const nodeComments = comments.enter(node);
const container = node.items.map((item, i) => {
if (item !== null) {
const sequenceItemComments = nodeComments.enter(item, i);
const walked = walkAST(ctx, item, sequenceItemComments, options);
sequenceItemComments.attachComments();
return walked;
}
else {
return null;
}
});
nodeComments.attachComments();
return container;
}
case types_2.Kind.SCALAR: {
const value = getScalarValue(node);
return !options.bigInt && typeof value === 'bigint' ? Number(value) : value;
}
case types_2.Kind.ANCHOR_REF: {
if (utils_1.isObject(node.value)) {
node.value = dereferenceAnchor_1.dereferenceAnchor(node.value, node.referencesAnchor);
}
return walkAST(ctx, node.value, comments, options);
}
default:
return null;
}
}
return node;
};
function getScalarValue(node) {
switch (yaml_ast_parser_1.determineScalarType(node)) {
case types_2.ScalarType.null:
return null;
case types_2.ScalarType.string:
return String(node.value);
case types_2.ScalarType.bool:
return yaml_ast_parser_1.parseYamlBoolean(node.value);
case types_2.ScalarType.int:
return yaml_ast_parser_1.parseYamlBigInteger(node.value);
case types_2.ScalarType.float:
return yaml_ast_parser_1.parseYamlFloat(node.value);
}
}
const computeLineMap = (input) => {
const lineMap = [];
let i = 0;
for (; i < input.length; i++) {
if (input[i] === '\n') {
lineMap.push(i + 1);
}
}
lineMap.push(i + 1);
return lineMap;
};
function getLineLength(lineMap, line) {
if (line === 0) {
return Math.max(0, lineMap[0] - 1);
}
return Math.max(0, lineMap[line] - lineMap[line - 1] - 1);
}
const transformErrors = (errors, lineMap) => {
const validations = [];
let possiblyUnexpectedFlow = -1;
let i = 0;
for (const error of errors) {
const validation = {
code: error.name,
message: error.reason,
severity: error.isWarning ? types_1.DiagnosticSeverity.Warning : types_1.DiagnosticSeverity.Error,
range: {
start: {
line: error.mark.line,
character: error.mark.column,
},
end: {
line: error.mark.line,
character: error.mark.toLineEnd ? getLineLength(lineMap, error.mark.line) : error.mark.column,
},
},
};
const isBrokenFlow = error.reason === 'missed comma between flow collection entries';
if (isBrokenFlow) {
possiblyUnexpectedFlow = possiblyUnexpectedFlow === -1 ? i : possiblyUnexpectedFlow;
}
else if (possiblyUnexpectedFlow !== -1) {
validations[possiblyUnexpectedFlow].range.end = validation.range.end;
validations[possiblyUnexpectedFlow].message = 'invalid mixed usage of block and flow styles';
validations.length = possiblyUnexpectedFlow + 1;
i = validations.length;
possiblyUnexpectedFlow = -1;
}
validations.push(validation);
i++;
}
return validations;
};
const reduceMergeKeys = (items, preserveKeyOrder) => {
if (Array.isArray(items)) {
const reduced = items.reduceRight(preserveKeyOrder
? (merged, item) => {
const keys = Object.keys(item);
Object.assign(merged, item);
for (let i = keys.length - 1; i >= 0; i--) {
unshiftKey(merged, keys[i]);
}
return merged;
}
: (merged, item) => Object.assign(merged, item), createMapContainer(preserveKeyOrder));
return reduced;
}
return typeof items !== 'object' || items === null ? null : Object(items);
};
function createMapContainer(preserveKeyOrder) {
return preserveKeyOrder ? ordered_object_literal_1.default({}) : {};
}
function deleteKey(container, key) {
if (!(key in container))
return;
const order = ordered_object_literal_1.getOrder(container);
const index = order.indexOf(key);
if (index !== -1) {
order.splice(index, 1);
}
}
function unshiftKey(container, key) {
deleteKey(container, key);
ordered_object_literal_1.getOrder(container).unshift(key);
}
function pushKey(container, key) {
deleteKey(container, key);
ordered_object_literal_1.getOrder(container).push(key);
}
function validateMappingKey(mapping, lineMap, diagnostics, yamlMode) {
if (mapping.key.kind !== types_2.Kind.SCALAR) {
if (!yamlMode) {
diagnostics.push(createYAMLIncompatibilityException(mapping.key, lineMap, 'mapping key must be a string scalar', yamlMode));
}
return false;
}
if (!yamlMode) {
const type = typeof getScalarValue(mapping.key);
if (type !== 'string') {
diagnostics.push(createYAMLIncompatibilityException(mapping.key, lineMap, `mapping key must be a string scalar rather than ${mapping.key.valueObject === null ? 'null' : type}`, yamlMode));
}
}
return true;
}
function createYAMLIncompatibilityException(node, lineMap, message, yamlMode) {
const exception = createYAMLException(node, lineMap, message);
exception.code = 'YAMLIncompatibleValue';
exception.severity = yamlMode ? types_1.DiagnosticSeverity.Hint : types_1.DiagnosticSeverity.Warning;
return exception;
}
function createYAMLException(node, lineMap, message) {
return {
code: 'YAMLException',
message,
severity: types_1.DiagnosticSeverity.Error,
path: buildJsonPath_1.buildJsonPath(node),
range: getRange(lineMap, node.startPosition, node.endPosition),
};
}
function getRange(lineMap, startPosition, endPosition) {
const startLine = lineForPosition_1.lineForPosition(startPosition, lineMap);
const endLine = lineForPosition_1.lineForPosition(endPosition, lineMap);
return {
start: {
line: startLine,
character: startLine === 0 ? startPosition : startPosition - lineMap[startLine - 1],
},
end: {
line: endLine,
character: endLine === 0 ? endPosition : endPosition - lineMap[endLine - 1],
},
};
}
class Comments {
constructor(attachedComments, comments, node, lineMap, pointer) {
this.attachedComments = attachedComments;
this.node = node;
this.lineMap = lineMap;
this.pointer = pointer;
if (comments.length === 0) {
this.comments = [];
}
else {
const startPosition = this.getStartPosition(node);
const endPosition = this.getEndPosition(node);
const startLine = lineForPosition_1.lineForPosition(startPosition, this.lineMap);
const endLine = lineForPosition_1.lineForPosition(endPosition, this.lineMap);
const matchingComments = [];
for (let i = comments.length - 1; i >= 0; i--) {
const comment = comments[i];
if (comment.range.start.line >= startLine && comment.range.end.line <= endLine) {
matchingComments.push(comment);
comments.splice(i, 1);
}
}
this.comments = matchingComments;
}
}
getStartPosition(node) {
if (node.parent === null) {
return 0;
}
return node.kind === types_2.Kind.MAPPING ? node.key.startPosition : node.startPosition;
}
getEndPosition(node) {
switch (node.kind) {
case types_2.Kind.MAPPING:
return node.value === null ? node.endPosition : this.getEndPosition(node.value);
case types_2.Kind.MAP:
return node.mappings.length === 0 ? node.endPosition : node.mappings[node.mappings.length - 1].endPosition;
case types_2.Kind.SEQ: {
if (node.items.length === 0) {
return node.endPosition;
}
const lastItem = node.items[node.items.length - 1];
return lastItem === null ? node.endPosition : lastItem.endPosition;
}
default:
return node.endPosition;
}
}
static mapComments(comments, lineMap) {
return comments.map(comment => ({
value: comment.value,
range: getRange(lineMap, comment.startPosition, comment.endPosition),
startPosition: comment.startPosition,
endPosition: comment.endPosition,
}));
}
enter(node, key) {
return new Comments(this.attachedComments, this.comments, node, this.lineMap, key === void 0 ? this.pointer : `${this.pointer}/${key}`);
}
static isLeading(node, startPosition) {
switch (node.kind) {
case types_2.Kind.MAP:
return node.mappings.length === 0 || node.mappings[0].startPosition > startPosition;
case types_2.Kind.SEQ: {
if (node.items.length === 0) {
return true;
}
const firstItem = node.items[0];
return firstItem === null || firstItem.startPosition > startPosition;
}
case types_2.Kind.MAPPING:
return node.value === null || node.value.startPosition > startPosition;
default:
return false;
}
}
static isTrailing(node, endPosition) {
switch (node.kind) {
case types_2.Kind.MAP:
return node.mappings.length > 0 && endPosition > node.mappings[node.mappings.length - 1].endPosition;
case types_2.Kind.SEQ:
if (node.items.length === 0) {
return false;
}
const lastItem = node.items[node.items.length - 1];
return lastItem !== null && endPosition > lastItem.endPosition;
case types_2.Kind.MAPPING:
return node.value !== null && endPosition > node.value.endPosition;
default:
return false;
}
}
static findBetween(node, startPosition, endPosition) {
switch (node.kind) {
case types_2.Kind.MAP: {
let left;
for (const mapping of node.mappings) {
if (startPosition > mapping.startPosition) {
left = mapping.key.value;
}
else if (left !== void 0 && mapping.startPosition > endPosition) {
return [left, mapping.key.value];
}
}
return null;
}
case types_2.Kind.SEQ: {
let left;
for (let i = 0; i < node.items.length; i++) {
const item = node.items[i];
if (item === null)
continue;
if (startPosition > item.startPosition) {
left = String(i);
}
else if (left !== void 0 && item.startPosition > endPosition) {
return [left, String(i)];
}
}
return null;
}
default:
return null;
}
}
isBeforeEOL(comment) {
return (this.node.kind === types_2.Kind.SCALAR ||
(this.node.kind === types_2.Kind.MAPPING &&
comment.range.end.line === lineForPosition_1.lineForPosition(this.node.key.endPosition, this.lineMap)));
}
attachComments() {
if (this.comments.length === 0)
return;
const attachedComments = (this.attachedComments[this.pointer] = this.attachedComments[this.pointer] || []);
for (const comment of this.comments) {
if (this.isBeforeEOL(comment)) {
attachedComments.push({
value: comment.value,
placement: 'before-eol',
});
}
else if (Comments.isLeading(this.node, comment.startPosition)) {
attachedComments.push({
value: comment.value,
placement: 'leading',
});
}
else if (Comments.isTrailing(this.node, comment.endPosition)) {
attachedComments.push({
value: comment.value,
placement: 'trailing',
});
}
else {
const between = Comments.findBetween(this.node, comment.startPosition, comment.endPosition);
if (between !== null) {
attachedComments.push({
value: comment.value,
placement: 'between',
between,
});
}
else {
attachedComments.push({
value: comment.value,
placement: 'trailing',
});
}
}
}
}
}
function normalizeOptions(options) {
if (options === void 0) {
return {
attachComments: false,
preserveKeyOrder: false,
bigInt: false,
mergeKeys: false,
json: true,
ignoreDuplicateKeys: false,
};
}
return Object.assign({}, options, { attachComments: options.attachComments === true, preserveKeyOrder: options.preserveKeyOrder === true, bigInt: options.bigInt === true, mergeKeys: options.mergeKeys === true, json: options.json !== false, ignoreDuplicateKeys: options.ignoreDuplicateKeys !== false });
}
//# sourceMappingURL=parseWithPointers.js.map