ecmarkup
Version:
Custom element definitions and core utilities for markup that specifies ECMAScript and related technologies.
144 lines (143 loc) • 6.17 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const Builder_1 = require("./Builder");
const Clause_1 = require("./Clause");
const utils_1 = require("./utils");
const utils_2 = require("./lint/utils");
const emd = require("ecmarkdown");
function findLabeledSteps(root) {
const steps = [];
emd.visit(root, {
enter(node) {
if (node.name === 'ordered-list-item' && node.attrs.some(a => a.key === 'id')) {
steps.push(node);
}
},
});
return steps;
}
const kindSelector = Clause_1.SPECIAL_KINDS.map(kind => `li[${kind}]`).join(',');
class Algorithm extends Builder_1.default {
static async enter(context) {
context.inAlg = true;
const { spec, node, clauseStack } = context;
let emdTree = null;
let innerHTML;
if ('ecmarkdownTree' in node) {
emdTree = node.ecmarkdownTree;
innerHTML = node.originalHtml;
}
else {
const location = spec.locate(node);
const source = (location === null || location === void 0 ? void 0 : location.source) == null || location.endTag == null
? node.innerHTML
: location.source.slice(location.startTag.endOffset, location.endTag.startOffset);
innerHTML = source;
try {
emdTree = emd.parseAlgorithm(source);
node.ecmarkdownTree = emdTree;
node.originalHtml = source;
}
catch (e) {
(0, utils_1.warnEmdFailure)(spec.warn, node, e);
}
}
if (emdTree == null) {
node.innerHTML = (0, utils_1.wrapEmdFailure)(innerHTML);
return;
}
if (spec.opts.lintSpec && spec.locate(node) != null && !node.hasAttribute('example')) {
const clause = clauseStack[clauseStack.length - 1];
const namespace = clause ? clause.namespace : spec.namespace;
const nonterminals = (0, utils_2.collectNonterminalsFromEmd)(emdTree).map(({ name, loc }) => ({
name,
loc,
node,
namespace,
}));
spec._ntStringRefs = spec._ntStringRefs.concat(nonterminals);
}
const rawHtml = emd.emit(emdTree);
// replace spaces after !/? with to prevent bad line breaking
let html = rawHtml.replace(/((?:\s+|>)[!?])[ \t]+/g, '$1 ');
// replace spaces before »/} with to prevent bad line breaking
html = html.replace(/[ \t]+([»}])/g, ' $1');
node.innerHTML = html;
const labeledStepEntries = [];
const replaces = node.getAttribute('replaces-step');
if (replaces) {
context.spec.replacementAlgorithms.push({
element: node,
target: replaces,
});
context.spec.replacementAlgorithmToContainedLabeledStepEntries.set(node, labeledStepEntries);
}
if (replaces && node.firstElementChild.children.length > 1) {
const labeledSteps = findLabeledSteps(emdTree);
for (const step of labeledSteps) {
const itemSource = innerHTML.slice(step.location.start.offset, step.location.end.offset);
const offset = itemSource.match(/^.*?[ ,[]id *= *"/)[0].length;
spec.warn({
type: 'contents',
ruleId: 'labeled-step-in-replacement',
message: 'labeling a step in a replacement algorithm which has multiple top-level steps is unsupported because the resulting step number would be ambiguous',
node,
nodeRelativeLine: step.location.start.line,
nodeRelativeColumn: step.location.start.column + offset,
});
}
}
for (const step of node.querySelectorAll('li[id]')) {
const entry = {
type: 'step',
id: step.id,
stepNumbers: getStepNumbers(step),
};
context.spec.biblio.add(entry);
if (replaces) {
// The biblio entries for labeled steps in replacement algorithms will be modified in-place by a subsequent pass
labeledStepEntries.push(entry);
context.spec.labeledStepsToBeRectified.add(step.id);
}
}
for (const step of node.querySelectorAll(kindSelector)) {
// prettier-ignore
const attributes = Clause_1.SPECIAL_KINDS
.filter(kind => step.hasAttribute(kind))
.map(kind => Clause_1.SPECIAL_KINDS_MAP.get(kind));
const tag = spec.doc.createElement('div');
tag.className = 'attributes-tag';
const text = attributes.join(', ');
const contents = spec.doc.createTextNode(text);
tag.append(contents);
step.prepend(tag);
// we've already walked past the text node, so it won't get picked up by the usual process for autolinking
const clause = clauseStack[clauseStack.length - 1];
if (clause != null) {
// the `== null` case only happens if you put an algorithm at the top level of your document
spec._textNodes[clause.namespace] = spec._textNodes[clause.namespace] || [];
spec._textNodes[clause.namespace].push({
node: contents,
clause,
inAlg: true,
currentId: context.currentId,
});
}
}
}
static exit(context) {
context.inAlg = false;
}
}
Algorithm.elements = ['EMU-ALG'];
exports.default = Algorithm;
function getStepNumbers(item) {
var _a;
const { indexOf } = Array.prototype;
const counts = [];
while (((_a = item.parentElement) === null || _a === void 0 ? void 0 : _a.tagName) === 'OL') {
counts.unshift(1 + indexOf.call(item.parentElement.children, item));
item = item.parentElement.parentElement;
}
return counts;
}
;