Skip to content

Commit 689f250

Browse files
committed
Fix bug
* `Array#splice` was misused, as its second parameter is a count, instead of an `end`. * Refactor to secure missing nodes in the by `onrun` returned nodes (can occur when `end` is returned by when it does not exist).
1 parent 374f848 commit 689f250

File tree

4 files changed

+268
-6
lines changed

4 files changed

+268
-6
lines changed

index.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ function search(root, test, callback) {
9292
var start = null;
9393
var end = null;
9494
var nodes;
95+
var clean;
9596
var child;
9697

9798
while (++index < length) {
@@ -124,8 +125,24 @@ function search(root, test, callback) {
124125
}
125126
);
126127

128+
clean = [];
129+
index = -1;
130+
length = nodes && nodes.length;
131+
132+
/*
133+
* Ensure no empty nodes are inserted. This could
134+
* be the case if `end` is in `nodes` but no `end`
135+
* node exists.
136+
*/
137+
138+
while (++index < length) {
139+
if (nodes[index]) {
140+
clean.push(nodes[index]);
141+
}
142+
}
143+
127144
if (nodes) {
128-
splice.apply(children, [start, end + 1].concat(nodes));
145+
splice.apply(children, [start, end - start + 1].concat(clean));
129146
}
130147
}
131148
}

mdast-util-heading-range.js

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.mdastUtilHeadingRange = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2+
'use strict';
3+
4+
/*
5+
* Dependencies.
6+
*/
7+
8+
var toString = require('mdast-util-to-string');
9+
10+
/*
11+
* Methods.
12+
*/
13+
14+
var splice = [].splice;
15+
16+
/**
17+
* Transform a string into an applicable expression.
18+
*
19+
* @param {string} value - Value to transform.
20+
* @return {RegExp}
21+
*/
22+
function toExpression(value) {
23+
return new RegExp('^(' + value + ')$', 'i');
24+
}
25+
26+
/**
27+
* Wrap an expression into an assertion function.
28+
*
29+
* @param {RegExp} expression - Expression to test.
30+
* @return {Function}
31+
*/
32+
function wrapExpression(expression) {
33+
/**
34+
* Assert `value` matches the bound `expression`.
35+
*
36+
* @param {string} value - Value to check.
37+
* @return {boolean}
38+
*/
39+
function assertion(value) {
40+
return expression.test(value);
41+
}
42+
43+
return assertion;
44+
}
45+
46+
/**
47+
* Check if `node` is a heading.
48+
*
49+
* @param {Node} node - Node to check.
50+
* @return {boolean}
51+
*/
52+
function isHeading(node) {
53+
return node && node.type === 'heading';
54+
}
55+
56+
/**
57+
* Check if `node` is the main heading.
58+
*
59+
* @param {Node} node - Node to check.
60+
* @param {number?} depth - Depth to search for.
61+
* @param {function(string): boolean} test - Tester.
62+
*
63+
* @return {boolean}
64+
*/
65+
function isOpeningHeading(node, depth, test) {
66+
return depth === null && isHeading(node) && test(toString(node), node);
67+
}
68+
69+
/**
70+
* Check if `node` is the next heading.
71+
*
72+
* @param {Node} node - Node to check.
73+
* @param {number?} depth - Depth of the opening heading.
74+
* @return {boolean}
75+
*/
76+
function isClosingHeading(node, depth) {
77+
return depth && isHeading(node) && node.depth <= depth;
78+
}
79+
80+
/**
81+
* Search a node for heading range.
82+
*
83+
* @param {Node} root - Node to search.
84+
* @param {Function} test - Assertion.
85+
* @param {Function} callback - Callback invoked when
86+
* found.
87+
*/
88+
function search(root, test, callback) {
89+
var index = -1;
90+
var children = root.children;
91+
var length = children.length;
92+
var depth = null;
93+
var start = null;
94+
var end = null;
95+
var nodes;
96+
var clean;
97+
var child;
98+
99+
while (++index < length) {
100+
child = children[index];
101+
102+
if (isClosingHeading(child, depth)) {
103+
end = index;
104+
break;
105+
}
106+
107+
if (isOpeningHeading(child, depth, test)) {
108+
start = index;
109+
depth = child.depth;
110+
}
111+
}
112+
113+
if (start !== null) {
114+
if (end === null) {
115+
end = length + 1;
116+
}
117+
118+
nodes = callback(
119+
children[start],
120+
children.slice(start + 1, end - start + 1),
121+
children[end],
122+
{
123+
'parent': root,
124+
'start': start,
125+
'end': end in children ? end : null
126+
}
127+
);
128+
129+
clean = [];
130+
index = -1;
131+
length = nodes && nodes.length;
132+
133+
/*
134+
* Ensure no empty nodes are inserted. This could
135+
* be the case if `end` is in `nodes` but no `end`
136+
* node exists.
137+
*/
138+
139+
while (++index < length) {
140+
if (nodes[index]) {
141+
clean.push(nodes[index]);
142+
}
143+
}
144+
145+
if (nodes) {
146+
splice.apply(children, [start, end - start + 1].concat(clean));
147+
}
148+
}
149+
}
150+
151+
/**
152+
* Wrapper.
153+
*
154+
* @param {string|RegExp|Function} heading - Heading to
155+
* search for.
156+
* @param {Function} callback - Callback invoked when
157+
* found.
158+
* @return {function(node)}
159+
*/
160+
function wrapper(heading, callback) {
161+
var test = heading;
162+
163+
if (typeof test === 'string') {
164+
test = toExpression(test);
165+
}
166+
167+
if ('test' in test) {
168+
test = wrapExpression(test);
169+
}
170+
171+
/**
172+
* Find a range based on a starting heading matching
173+
* `test`, up until the following closing with
174+
* equal or higher depth.
175+
*
176+
* @param {Node} node - Node to search in.
177+
*/
178+
function transformer(node) {
179+
search(node, test, callback);
180+
}
181+
182+
/**
183+
* `Attacher`.
184+
*/
185+
function attacher() {
186+
return transformer;
187+
}
188+
189+
return attacher;
190+
}
191+
192+
/*
193+
* Expose.
194+
*/
195+
196+
module.exports = wrapper;
197+
198+
},{"mdast-util-to-string":2}],2:[function(require,module,exports){
199+
/**
200+
* @author Titus Wormer
201+
* @copyright 2015 Titus Wormer. All rights reserved.
202+
* @module mdast:util:to-string
203+
* @fileoverview Utility to get the text value of a node.
204+
*/
205+
206+
'use strict';
207+
208+
/**
209+
* Get the value of `node`. Checks, `value`,
210+
* `alt`, and `title`, in that order.
211+
*
212+
* @param {Node} node - Node to get the internal value of.
213+
* @return {string} - Textual representation.
214+
*/
215+
function valueOf(node) {
216+
return node &&
217+
(node.value ? node.value :
218+
(node.alt ? node.alt : node.title)) || '';
219+
}
220+
221+
/**
222+
* Returns the text content of a node. If the node itself
223+
* does not expose plain-text fields, `toString` will
224+
* recursivly try its children.
225+
*
226+
* @param {Node} node - Node to transform to a string.
227+
* @return {string} - Textual representation.
228+
*/
229+
function toString(node) {
230+
return valueOf(node) ||
231+
(node.children && node.children.map(toString).join('')) ||
232+
'';
233+
}
234+
235+
/*
236+
* Expose.
237+
*/
238+
239+
module.exports = toString;
240+
241+
},{}]},{},[1])(1)
242+
});

mdast-util-heading-range.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,25 @@ describe('mdast-util-heading-range(heading, callback)', function () {
248248
return [];
249249
}));
250250
}).process('Foo\n\n## Foo\n\nBar\n', function (exception, file, doc) {
251-
equal(doc, 'Foo\n');
252-
253251
done(exception);
252+
253+
equal(doc, 'Foo\n');
254254
});
255255
});
256256

257257
it('should insert all returned nodes', function (done) {
258258
mdast().use(function (processor) {
259-
processor.use(heading('foo', function () {
259+
processor.use(heading('foo', function (start, nodes, end) {
260260
return [
261+
start,
261262
{
262263
'type': 'horizontalRule'
263-
}
264+
},
265+
end
264266
];
265267
}));
266268
}).process('Foo\n\n## Foo\n\nBar\n', function (exception, file, doc) {
267-
equal(doc, 'Foo\n\n* * *\n');
269+
equal(doc, 'Foo\n\n## Foo\n\n* * *\n');
268270

269271
done(exception);
270272
});

0 commit comments

Comments
 (0)