all files / es6/modules/ expand-pair-trait.js

98.81% Statements 83/84
97.3% Branches 36/37
100% Functions 8/8
98.8% Lines 82/83
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                                 85×   10×   75×           264× 264× 264× 196×   68× 68× 68× 66× 85× 85× 85× 65×   65× 65×               196× 196× 982×   196× 196× 196×   194× 61× 61× 54×   61× 46×   15× 15× 15×     194× 194× 194× 5122× 5122× 5122× 5122× 3623× 3623×   1499× 61×   1499× 1377×   1499× 61× 61× 61× 61× 61× 61×   1499×         90×  
const traitName = "expandPair";
const mergeSort = require("../mergesort");
const DocUtils = require("../doc-utils");
const wrapper = require("../module-wrapper");
const {getExpandToDefault} = require("../traits");
const Errors = require("../errors");
 
function getUnmatchedLoopException(options) {
	const location = options.location;
	const t = location === "start" ? "unclosed" : "unopened";
	const T = location === "start" ? "Unclosed" : "Unopened";
 
	const err = new Errors.XTTemplateError(`${T} loop`);
	const tag = options.part.value;
	err.properties = {
		id: `${t}_loop`,
		explanation: `The loop with tag ${tag} is ${t}`,
		xtag: tag,
	};
	return err;
}
 
function getClosingTagNotMatchOpeningTag(options) {
	const tags = options.tags;
 
	const err = new Errors.XTTemplateError("Closing tag does not match opening tag");
	err.properties = {
		id: "closing_tag_does_not_match_opening_tag",
		explanation: `The tag "${tags[0].value}" is closed by the tag "${tags[1].value}"`,
		openingtag: tags[0].value,
		closingtag: tags[1].value,
	};
	return err;
}
 
function getOpenCountChange(part) {
	switch (part.location) {
		case "start":
			return 1;
		case "end":
			return -1;
		default:
			throw new Error(`Location should be one of 'start' or 'end' (given : ${part.location})`);
	}
}
 
function getPairs(traits) {
	const errors = [];
	const pairs = [];
	if (traits.length === 0) {
		return {pairs, errors};
	}
	let countOpen = 1;
	const firstTrait = traits[0];
	if (firstTrait.part.location === "start") {
		for (let i = 1; i < traits.length; i++) {
			const currentTrait = traits[i];
			countOpen += getOpenCountChange(currentTrait.part);
			if (countOpen === 0) {
				if (currentTrait.part.value !== firstTrait.part.value && currentTrait.part.value !== "") {
					errors.push(getClosingTagNotMatchOpeningTag({tags: [firstTrait.part, currentTrait.part]}));
				}
				const outer = getPairs(traits.slice(i + 1));
				return {pairs: [[firstTrait, currentTrait]].concat(outer.pairs), errors: errors.concat(outer.errors)};
			}
		}
	}
	const part = firstTrait.part;
	errors.push(getUnmatchedLoopException({part, location: part.location}));
	const outer = getPairs(traits.slice(1));
	return {pairs: outer.pairs, errors: errors.concat(outer.errors)};
}
 
const expandPairTrait = {
	name: "ExpandPairTrait",
	postparse(parsed, {getTraits, postparse}) {
		let traits = getTraits(traitName, parsed);
		traits = traits.map(function (trait) {
			return trait || [];
		});
		traits = mergeSort(traits);
		const {pairs, errors} = getPairs(traits);
		if (errors.length >= 1) {
			throw Errors.throwMultiError(errors);
		}
		const expandedPairs = pairs.map(function (pair) {
			let expandTo = pair[0].part.expandTo;
			if (expandTo === "auto") {
				expandTo = getExpandToDefault(parsed.slice(pair[0].offset, pair[1].offset));
			}
			if (!expandTo) {
				return [pair[0].offset, pair[1].offset];
			}
			const left = DocUtils.getLeft(parsed, expandTo, pair[0].offset);
			const right = DocUtils.getRight(parsed, expandTo, pair[1].offset);
			return [left, right];
		});
 
		let currentPairIndex = 0;
		let innerParts;
		return parsed.reduce(function (newParsed, part, i) {
			const inPair = currentPairIndex < pairs.length && expandedPairs[currentPairIndex][0] <= i;
			const pair = pairs[currentPairIndex];
			const expandedPair = expandedPairs[currentPairIndex];
			if(!inPair) {
				newParsed.push(part);
				return newParsed;
			}
			if (expandedPair[0] === i) {
				innerParts = [];
			}
			if (pair[0].offset !== i && pair[1].offset !== i) {
				innerParts.push(part);
			}
			if (expandedPair[1] === i) {
				const basePart = parsed[pair[0].offset];
				delete basePart.location;
				delete basePart.expandTo;
				basePart.subparsed = postparse(innerParts);
				newParsed.push(basePart);
				currentPairIndex++;
			}
			return newParsed;
		}, []);
	},
};
 
module.exports = () => wrapper(expandPairTrait);