| import { anchorIsValid } from '../doc/anchors.js'; | |
| import { visit } from '../visit.js'; | |
| import { ALIAS, isAlias, isCollection, isPair, hasAnchor } 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, ctx) { | |
| if (ctx?.maxAliasCount === 0) | |
| throw new ReferenceError('Alias resolution is disabled'); | |
| let nodes; | |
| if (ctx?.aliasResolveCache) { | |
| nodes = ctx.aliasResolveCache; | |
| } | |
| else { | |
| nodes = []; | |
| visit(doc, { | |
| Node: (_key, node) => { | |
| if (isAlias(node) || hasAnchor(node)) | |
| nodes.push(node); | |
| } | |
| }); | |
| if (ctx) | |
| ctx.aliasResolveCache = nodes; | |
| } | |
| let found = undefined; | |
| for (const node of nodes) { | |
| if (node === this) | |
| 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, ctx); | |
| 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?.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 }; | |
Xet Storage Details
- Size:
- 3.86 kB
- Xet hash:
- a871c27d205eb96a64c6901528c8f4744f4ef8ba89cd65e0b6dc266389871a78
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.