|
1 | 1 | "use strict"; |
2 | 2 |
|
3 | | -var P = require('bluebird'); |
| 3 | +const P = require('bluebird'); |
| 4 | + |
| 5 | +// Work around recursive structure in ** terminal nodes |
| 6 | +function printableValue(value) { |
| 7 | + const res = {}; |
| 8 | + if (!value || !(value instanceof Object)) { |
| 9 | + return value; |
| 10 | + } |
| 11 | + Object.keys(value).forEach((key) => { |
| 12 | + const val = value[key]; |
| 13 | + if (key === 'methods') { |
| 14 | + const newMethods = {}; |
| 15 | + Object.keys(val).forEach((method) => { |
| 16 | + newMethods[method] = `<${val[method].name}>`; |
| 17 | + }); |
| 18 | + res.methods = newMethods; |
| 19 | + } else if (key !== 'specRoot') { |
| 20 | + // Omit the specRoot, as it tends to be huge & contains reference |
| 21 | + // circles. |
| 22 | + res[key] = val; |
| 23 | + } |
| 24 | + }); |
| 25 | + return res; |
| 26 | +} |
| 27 | + |
| 28 | +const _keyPrefix = '/'; |
| 29 | +const _keyPrefixRegExp = /^\//; |
4 | 30 |
|
5 | 31 | /* |
6 | 32 | * A node in the lookup graph. |
7 | 33 | * |
8 | 34 | * We use a single monomorphic type for the JIT's benefit. |
9 | 35 | */ |
10 | | -function Node(value) { |
11 | | - // The value for a path ending on this node. Public property. |
12 | | - this.value = value || null; |
13 | | - |
14 | | - // Internal properties. |
15 | | - this._children = {}; |
16 | | - this._paramName = null; |
17 | | - this._parent = null; |
18 | | -} |
19 | | - |
20 | | -Node.prototype._keyPrefix = '/'; |
21 | | -Node.prototype._keyPrefixRegExp = /^\//; |
| 36 | +class Node { |
| 37 | + constructor(value) { |
| 38 | + // The value for a path ending on this node. Public property. |
| 39 | + this.value = value || null; |
| 40 | + |
| 41 | + // Internal properties. |
| 42 | + this._children = {}; |
| 43 | + this._paramName = null; |
| 44 | + this._parent = null; |
| 45 | + } |
22 | 46 |
|
23 | | -Node.prototype.setChild = function(key, child) { |
24 | | - var self = this; |
25 | | - if (key.constructor === String) { |
26 | | - this._children[this._keyPrefix + key] = child; |
27 | | - } else if (key.name && key.pattern |
28 | | - && key.modifier !== '+' |
29 | | - && key.pattern.constructor === String) { |
30 | | - // A named but plain key. |
31 | | - child._paramName = key.name; |
32 | | - this._children[this._keyPrefix + key.pattern] = child; |
33 | | - } else if (key.modifier === '+') { |
34 | | - child._paramName = key.name; |
35 | | - this._children['**'] = child; |
36 | | - } else { |
37 | | - // Setting up a wildcard match |
38 | | - child._paramName = key.name; |
39 | | - this._children['*'] = child; |
| 47 | + setChild(key, child) { |
| 48 | + if (key.constructor === String) { |
| 49 | + this._children[_keyPrefix + key] = child; |
| 50 | + } else if (key.name && key.pattern |
| 51 | + && key.modifier !== '+' |
| 52 | + && key.pattern.constructor === String) { |
| 53 | + // A named but plain key. |
| 54 | + child._paramName = key.name; |
| 55 | + this._children[_keyPrefix + key.pattern] = child; |
| 56 | + } else if (key.modifier === '+') { |
| 57 | + child._paramName = key.name; |
| 58 | + this._children['**'] = child; |
| 59 | + } else { |
| 60 | + // Setting up a wildcard match |
| 61 | + child._paramName = key.name; |
| 62 | + this._children['*'] = child; |
| 63 | + } |
40 | 64 | } |
41 | | -}; |
42 | 65 |
|
43 | | -Node.prototype.getChild = function(segment, params) { |
44 | | - if (segment.constructor === String) { |
45 | | - // Fast path |
46 | | - var res = this._children[this._keyPrefix + segment]; |
47 | | - if (!res) { |
48 | | - if (segment !== '') { |
| 66 | + getChild(segment, params) { |
| 67 | + if (segment.constructor === String) { |
| 68 | + // Fast path |
| 69 | + let res = this._children[_keyPrefix + segment]; |
| 70 | + if (!res && segment !== '') { |
49 | 71 | // Fall back to the wildcard match, but only if the segment is |
50 | 72 | // non-empty. |
51 | 73 | res = this._children['*']; |
52 | 74 | if (!res && this._children['**']) { |
53 | 75 | res = this._children['**']; |
54 | 76 | // Build up an array for ** matches ({+foo}) |
55 | 77 | if (params[res._paramName]) { |
56 | | - params[res._paramName] += '/' + encodeURIComponent(segment); |
| 78 | + params[res._paramName] += `/${encodeURIComponent(segment)}`; |
57 | 79 | } else { |
58 | 80 | params[res._paramName] = encodeURIComponent(segment); |
59 | 81 | } |
60 | 82 | // We are done. |
61 | 83 | return res; |
62 | 84 | } |
63 | 85 | } |
64 | | - } |
65 | 86 |
|
66 | | - if (res) { |
67 | | - if (res._paramName) { |
68 | | - params[res._paramName] = segment; |
| 87 | + if (res) { |
| 88 | + if (res._paramName) { |
| 89 | + params[res._paramName] = segment; |
| 90 | + } |
| 91 | + return res; |
| 92 | + } else { |
| 93 | + return null; |
69 | 94 | } |
70 | | - return res; |
71 | | - } else { |
72 | | - return null; |
73 | | - } |
74 | 95 |
|
75 | | - // Fall-back cases for internal use during tree construction. These cases |
76 | | - // are never used for actual routing. |
77 | | - } else if (segment.pattern) { |
78 | | - // Unwrap the pattern |
79 | | - return this.getChild(segment.pattern, params); |
80 | | - } else if (this._children['*'] |
81 | | - && this._children['*']._paramName === segment.name) { |
82 | | - // XXX: also compare modifier! |
83 | | - return this._children['*'] || null; |
| 96 | + // Fall-back cases for internal use during tree construction. These cases |
| 97 | + // are never used for actual routing. |
| 98 | + } else if (segment.pattern) { |
| 99 | + // Unwrap the pattern |
| 100 | + return this.getChild(segment.pattern, params); |
| 101 | + } else if (this._children['*'] |
| 102 | + && this._children['*']._paramName === segment.name) { |
| 103 | + // XXX: also compare modifier! |
| 104 | + return this._children['*'] || null; |
| 105 | + } |
84 | 106 | } |
85 | | -}; |
86 | 107 |
|
87 | | -Node.prototype.hasChildren = function() { |
88 | | - return Object.keys(this._children).length || this._children['*']; |
89 | | -}; |
90 | | - |
91 | | -Node.prototype.keys = function() { |
92 | | - var self = this; |
93 | | - if (this._children['*'] || this._children['**']) { |
94 | | - return []; |
95 | | - } else { |
96 | | - var res = []; |
97 | | - Object.keys(this._children).forEach(function(key) { |
98 | | - // Only list '' if there are children (for paths like |
99 | | - // /double//slash) |
100 | | - if (key !== self._keyPrefix || self._children[key].hasChildren()) { |
101 | | - res.push(key.replace(self._keyPrefixRegExp, '')); |
102 | | - } |
103 | | - }); |
104 | | - return res.sort(); |
| 108 | + hasChildren() { |
| 109 | + return Object.keys(this._children).length || this._children['*']; |
105 | 110 | } |
106 | | -}; |
107 | 111 |
|
108 | | -// Shallow clone, allows sharing of subtrees in DAG |
109 | | -Node.prototype.clone = function() { |
110 | | - var c = new Node(); |
111 | | - c._children = this._children; |
112 | | - c._paramName = this._paramName; |
113 | | - return c; |
114 | | -}; |
| 112 | + keys() { |
| 113 | + if (this._children['*'] || this._children['**']) { |
| 114 | + return []; |
| 115 | + } else { |
| 116 | + const res = []; |
| 117 | + Object.keys(this._children).forEach((key) => { |
| 118 | + // Only list '' if there are children (for paths like |
| 119 | + // /double//slash) |
| 120 | + if (key !== _keyPrefix || this._children[key].hasChildren()) { |
| 121 | + res.push(key.replace(_keyPrefixRegExp, '')); |
| 122 | + } |
| 123 | + }); |
| 124 | + return res.sort(); |
| 125 | + } |
| 126 | + } |
115 | 127 |
|
| 128 | + // Shallow clone, allows sharing of subtrees in DAG |
| 129 | + clone() { |
| 130 | + const c = new Node(); |
| 131 | + c._children = this._children; |
| 132 | + c._paramName = this._paramName; |
| 133 | + return c; |
| 134 | + } |
116 | 135 |
|
117 | | -// Call promise-returning fn for each node value, with the path to the value |
118 | | -Node.prototype.visitAsync = function(fn, path) { |
119 | | - path = path || []; |
120 | | - var self = this; |
121 | | - // First value, then each of the children (one by one) |
122 | | - return fn(self.value, path) |
123 | | - .then(function() { |
124 | | - return P.resolve(Object.keys(self._children)) |
125 | | - .each(function(childKey) { |
126 | | - var segment = childKey.replace(/^\//, ''); |
127 | | - var child = self._children[childKey]; |
128 | | - if (child === self) { |
| 136 | + // Call promise-returning fn for each node value, with the path to the value |
| 137 | + visitAsync(fn, path) { |
| 138 | + path = path || []; |
| 139 | + // First value, then each of the children (one by one) |
| 140 | + return fn(this.value, path) |
| 141 | + .then(() => P.resolve(Object.keys(this._children)) |
| 142 | + .each((childKey) => { |
| 143 | + const segment = childKey.replace(/^\//, ''); |
| 144 | + const child = this._children[childKey]; |
| 145 | + if (child === this) { |
129 | 146 | // Don't enter an infinite loop on ** |
130 | 147 | return; |
131 | 148 | } else { |
132 | 149 | return child.visitAsync(fn, path.concat([segment])); |
133 | 150 | } |
134 | | - }); |
135 | | - }); |
136 | | -}; |
137 | | - |
138 | | -// Work around recursive structure in ** terminal nodes |
139 | | -function printableValue(value) { |
140 | | - var res = {}; |
141 | | - if (!value || !(value instanceof Object)) { |
142 | | - return value; |
| 151 | + })); |
143 | 152 | } |
144 | | - Object.keys(value).forEach(function(key) { |
145 | | - var val = value[key]; |
146 | | - if (key === 'methods') { |
147 | | - var newMethods = {}; |
148 | | - Object.keys(val).forEach(function(method) { |
149 | | - newMethods[method] = '<' + val[method].name + '>'; |
150 | | - }); |
151 | | - res.methods = newMethods; |
152 | | - } else if (key !== 'specRoot') { |
153 | | - // Omit the specRoot, as it tends to be huge & contains reference |
154 | | - // circles. |
155 | | - res[key] = val; |
156 | | - } |
157 | | - }); |
158 | | - return res; |
159 | | -} |
160 | 153 |
|
161 | | -Node.prototype.toJSON = function() { |
162 | | - if (this._children['**'] === this) { |
163 | | - return { |
164 | | - value: printableValue(this.value), |
165 | | - _children: '<recursive>', |
166 | | - _paramName: this._paramName |
167 | | - }; |
168 | | - } else { |
169 | | - return { |
170 | | - value: printableValue(this.value), |
171 | | - _children: this._children, |
172 | | - _paramName: this._paramName |
173 | | - }; |
| 154 | + toJSON() { |
| 155 | + if (this._children['**'] === this) { |
| 156 | + return { |
| 157 | + value: printableValue(this.value), |
| 158 | + _children: '<recursive>', |
| 159 | + _paramName: this._paramName |
| 160 | + }; |
| 161 | + } else { |
| 162 | + return { |
| 163 | + value: printableValue(this.value), |
| 164 | + _children: this._children, |
| 165 | + _paramName: this._paramName |
| 166 | + }; |
| 167 | + } |
174 | 168 | } |
175 | | -}; |
176 | | - |
| 169 | +} |
177 | 170 |
|
178 | 171 | module.exports = Node; |
0 commit comments