1 | 'use strict';
|
---|
2 |
|
---|
3 | var anchors = require('../doc/anchors.js');
|
---|
4 | var visit = require('../visit.js');
|
---|
5 | var identity = require('./identity.js');
|
---|
6 | var Node = require('./Node.js');
|
---|
7 | var toJS = require('./toJS.js');
|
---|
8 |
|
---|
9 | class Alias extends Node.NodeBase {
|
---|
10 | constructor(source) {
|
---|
11 | super(identity.ALIAS);
|
---|
12 | this.source = source;
|
---|
13 | Object.defineProperty(this, 'tag', {
|
---|
14 | set() {
|
---|
15 | throw new Error('Alias nodes cannot have tags');
|
---|
16 | }
|
---|
17 | });
|
---|
18 | }
|
---|
19 | /**
|
---|
20 | * Resolve the value of this alias within `doc`, finding the last
|
---|
21 | * instance of the `source` anchor before this node.
|
---|
22 | */
|
---|
23 | resolve(doc) {
|
---|
24 | let found = undefined;
|
---|
25 | visit.visit(doc, {
|
---|
26 | Node: (_key, node) => {
|
---|
27 | if (node === this)
|
---|
28 | return visit.visit.BREAK;
|
---|
29 | if (node.anchor === this.source)
|
---|
30 | found = node;
|
---|
31 | }
|
---|
32 | });
|
---|
33 | return found;
|
---|
34 | }
|
---|
35 | toJSON(_arg, ctx) {
|
---|
36 | if (!ctx)
|
---|
37 | return { source: this.source };
|
---|
38 | const { anchors, doc, maxAliasCount } = ctx;
|
---|
39 | const source = this.resolve(doc);
|
---|
40 | if (!source) {
|
---|
41 | const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
|
---|
42 | throw new ReferenceError(msg);
|
---|
43 | }
|
---|
44 | let data = anchors.get(source);
|
---|
45 | if (!data) {
|
---|
46 | // Resolve anchors for Node.prototype.toJS()
|
---|
47 | toJS.toJS(source, null, ctx);
|
---|
48 | data = anchors.get(source);
|
---|
49 | }
|
---|
50 | /* istanbul ignore if */
|
---|
51 | if (!data || data.res === undefined) {
|
---|
52 | const msg = 'This should not happen: Alias anchor was not resolved?';
|
---|
53 | throw new ReferenceError(msg);
|
---|
54 | }
|
---|
55 | if (maxAliasCount >= 0) {
|
---|
56 | data.count += 1;
|
---|
57 | if (data.aliasCount === 0)
|
---|
58 | data.aliasCount = getAliasCount(doc, source, anchors);
|
---|
59 | if (data.count * data.aliasCount > maxAliasCount) {
|
---|
60 | const msg = 'Excessive alias count indicates a resource exhaustion attack';
|
---|
61 | throw new ReferenceError(msg);
|
---|
62 | }
|
---|
63 | }
|
---|
64 | return data.res;
|
---|
65 | }
|
---|
66 | toString(ctx, _onComment, _onChompKeep) {
|
---|
67 | const src = `*${this.source}`;
|
---|
68 | if (ctx) {
|
---|
69 | anchors.anchorIsValid(this.source);
|
---|
70 | if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) {
|
---|
71 | const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
|
---|
72 | throw new Error(msg);
|
---|
73 | }
|
---|
74 | if (ctx.implicitKey)
|
---|
75 | return `${src} `;
|
---|
76 | }
|
---|
77 | return src;
|
---|
78 | }
|
---|
79 | }
|
---|
80 | function getAliasCount(doc, node, anchors) {
|
---|
81 | if (identity.isAlias(node)) {
|
---|
82 | const source = node.resolve(doc);
|
---|
83 | const anchor = anchors && source && anchors.get(source);
|
---|
84 | return anchor ? anchor.count * anchor.aliasCount : 0;
|
---|
85 | }
|
---|
86 | else if (identity.isCollection(node)) {
|
---|
87 | let count = 0;
|
---|
88 | for (const item of node.items) {
|
---|
89 | const c = getAliasCount(doc, item, anchors);
|
---|
90 | if (c > count)
|
---|
91 | count = c;
|
---|
92 | }
|
---|
93 | return count;
|
---|
94 | }
|
---|
95 | else if (identity.isPair(node)) {
|
---|
96 | const kc = getAliasCount(doc, node.key, anchors);
|
---|
97 | const vc = getAliasCount(doc, node.value, anchors);
|
---|
98 | return Math.max(kc, vc);
|
---|
99 | }
|
---|
100 | return 1;
|
---|
101 | }
|
---|
102 |
|
---|
103 | exports.Alias = Alias;
|
---|