102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
|
import { anchorIsValid } from '../doc/anchors.js';
|
||
|
import { visit } from '../visit.js';
|
||
|
import { ALIAS, isAlias, isCollection, isPair } from './identity.js';
|
||
|
import { NodeBase } from './Node.js';
|
||
|
import { toJS } from './toJS.js';
|
||
|
|
||
|
class Alias extends NodeBase {
|
||
|
constructor(source) {
|
||
|
super(ALIAS);
|
||
|
this.source = source;
|
||
|
Object.defineProperty(this, 'tag', {
|
||
|
set() {
|
||
|
throw new Error('Alias nodes cannot have tags');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Resolve the value of this alias within `doc`, finding the last
|
||
|
* instance of the `source` anchor before this node.
|
||
|
*/
|
||
|
resolve(doc) {
|
||
|
let found = undefined;
|
||
|
visit(doc, {
|
||
|
Node: (_key, node) => {
|
||
|
if (node === this)
|
||
|
return visit.BREAK;
|
||
|
if (node.anchor === this.source)
|
||
|
found = node;
|
||
|
}
|
||
|
});
|
||
|
return found;
|
||
|
}
|
||
|
toJSON(_arg, ctx) {
|
||
|
if (!ctx)
|
||
|
return { source: this.source };
|
||
|
const { anchors, doc, maxAliasCount } = ctx;
|
||
|
const source = this.resolve(doc);
|
||
|
if (!source) {
|
||
|
const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
|
||
|
throw new ReferenceError(msg);
|
||
|
}
|
||
|
let data = anchors.get(source);
|
||
|
if (!data) {
|
||
|
// Resolve anchors for Node.prototype.toJS()
|
||
|
toJS(source, null, ctx);
|
||
|
data = anchors.get(source);
|
||
|
}
|
||
|
/* istanbul ignore if */
|
||
|
if (!data || data.res === undefined) {
|
||
|
const msg = 'This should not happen: Alias anchor was not resolved?';
|
||
|
throw new ReferenceError(msg);
|
||
|
}
|
||
|
if (maxAliasCount >= 0) {
|
||
|
data.count += 1;
|
||
|
if (data.aliasCount === 0)
|
||
|
data.aliasCount = getAliasCount(doc, source, anchors);
|
||
|
if (data.count * data.aliasCount > maxAliasCount) {
|
||
|
const msg = 'Excessive alias count indicates a resource exhaustion attack';
|
||
|
throw new ReferenceError(msg);
|
||
|
}
|
||
|
}
|
||
|
return data.res;
|
||
|
}
|
||
|
toString(ctx, _onComment, _onChompKeep) {
|
||
|
const src = `*${this.source}`;
|
||
|
if (ctx) {
|
||
|
anchorIsValid(this.source);
|
||
|
if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) {
|
||
|
const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
|
||
|
throw new Error(msg);
|
||
|
}
|
||
|
if (ctx.implicitKey)
|
||
|
return `${src} `;
|
||
|
}
|
||
|
return src;
|
||
|
}
|
||
|
}
|
||
|
function getAliasCount(doc, node, anchors) {
|
||
|
if (isAlias(node)) {
|
||
|
const source = node.resolve(doc);
|
||
|
const anchor = anchors && source && anchors.get(source);
|
||
|
return anchor ? anchor.count * anchor.aliasCount : 0;
|
||
|
}
|
||
|
else if (isCollection(node)) {
|
||
|
let count = 0;
|
||
|
for (const item of node.items) {
|
||
|
const c = getAliasCount(doc, item, anchors);
|
||
|
if (c > count)
|
||
|
count = c;
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
else if (isPair(node)) {
|
||
|
const kc = getAliasCount(doc, node.key, anchors);
|
||
|
const vc = getAliasCount(doc, node.value, anchors);
|
||
|
return Math.max(kc, vc);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
export { Alias };
|