Skip to content

Commit 235866a

Browse files
committed
Fix sibling combinators for non-elements
Closes GH-3.
1 parent edd8fc5 commit 235866a

File tree

4 files changed

+189
-102
lines changed

4 files changed

+189
-102
lines changed

lib/nest.js

Lines changed: 77 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ function child(query, node, index, parent, state) {
6565
return
6666
}
6767

68-
walkIterator(query, node, state)
69-
.each()
70-
.done()
68+
indexedSearch(query, node, state)
7169
}
7270

7371
function nextSibling(query, node, index, parent, state) {
@@ -76,11 +74,7 @@ function nextSibling(query, node, index, parent, state) {
7674
return
7775
}
7876

79-
walkIterator(query, parent, state)
80-
.prefillTypeIndex(0, ++index)
81-
.each(index, ++index)
82-
.prefillTypeIndex(index)
83-
.done()
77+
indexedSearch(query, parent, state, index + 1, true)
8478
}
8579

8680
function subsequentSibling(query, node, index, parent, state) {
@@ -89,132 +83,116 @@ function subsequentSibling(query, node, index, parent, state) {
8983
return
9084
}
9185

92-
walkIterator(query, parent, state)
93-
.prefillTypeIndex(0, ++index)
94-
.each(index)
95-
.done()
86+
indexedSearch(query, parent, state, index + 1)
9687
}
9788

9889
// Handles `typeIndex` and `typeCount` properties for every walker.
99-
function walkIterator(query, parent, state) {
100-
var nodes = parent.children
101-
var typeIndex = state.index ? createTypeIndex() : null
90+
function indexedSearch(query, parent, state, from, firstElementOnly) {
91+
var needsIndex = state.index
92+
var children = parent.children
93+
var length = children.length
10294
var delayed = []
103-
104-
return {
105-
prefillTypeIndex: rangeDefaults(prefillTypeIndex),
106-
each: rangeDefaults(each),
107-
done: done
95+
var index = 0
96+
var types = {}
97+
var elements = 0
98+
var handle = needsIndex ? delay : add
99+
var child
100+
101+
// Start looking at `from`
102+
if (from === undefined) {
103+
from = 0
108104
}
109105

110-
function done() {
111-
var length = delayed.length
112-
var index = -1
106+
// Exit if there are no further nodes.
107+
if (from >= length) {
108+
return
109+
}
113110

114-
while (++index < length) {
115-
delayed[index]()
111+
// If we need to index for types, do so for all elements before `from`.
112+
if (needsIndex) {
113+
while (index < from) {
114+
child = children[index]
116115

117-
if (state.one && state.found) {
118-
break
116+
if (child.type === 'element') {
117+
count(child.tagName)
119118
}
120-
}
121119

122-
return this
123-
}
124-
125-
function prefillTypeIndex(start, end) {
126-
if (typeIndex) {
127-
while (start < end) {
128-
typeIndex(nodes[start])
129-
start++
130-
}
120+
index++
131121
}
132-
133-
return this
134122
}
135123

136-
function each(start, end) {
137-
var child = nodes[start]
138-
var index
139-
var elementIndex
140-
141-
if (start >= end) {
142-
return this
143-
}
124+
index = from
144125

145-
if (typeIndex) {
146-
elementIndex = typeIndex.elements
147-
index = typeIndex(child)
148-
delayed.push(delay)
149-
} else {
150-
pushNode()
151-
}
126+
while (index < length) {
127+
child = children[index]
152128

153-
// Stop if we’re looking for one node and it’s already found.
154-
if (state.one && state.found) {
155-
return this
156-
}
129+
// Only check elements.
130+
// Check either all elements, or only check the first sibling
131+
if (child.type === 'element') {
132+
handle(child, index)
157133

158-
return each.call(this, start + 1, end)
134+
// Stop if we’re looking for one node and it’s already found.
135+
if (state.one && state.found) {
136+
return
137+
}
159138

160-
function delay() {
161-
state.typeIndex = index
162-
state.elementIndex = elementIndex
163-
state.typeCount = typeIndex.count(child)
164-
state.elementCount = typeIndex.elements
165-
pushNode()
139+
if (firstElementOnly) {
140+
break
141+
}
166142
}
167143

168-
function pushNode() {
169-
var exit = enter(state, child)
170-
state.iterator(query, child, start, parent, state)
171-
exit()
172-
}
144+
index++
173145
}
174146

175-
function rangeDefaults(iterator) {
176-
return rangeDefault
147+
if (needsIndex) {
148+
index = -1
149+
length = delayed.length
177150

178-
function rangeDefault(start, end) {
179-
if (start === null || start === undefined || start < 0) {
180-
start = 0
181-
}
151+
while (++index < length) {
152+
delayed[index]()
182153

183-
if (end === null || end === undefined || end > nodes.length) {
184-
end = nodes.length
154+
// Stop if we’re looking for one node and it’s already found.
155+
if (state.one && state.found) {
156+
// To do: maybe return?
157+
return
185158
}
186-
187-
return iterator.call(this, start, end)
188159
}
189160
}
190-
}
191-
192-
function createTypeIndex() {
193-
var counts = {}
194161

195-
index.count = count
196-
index.elements = 0
162+
function delay(node, childIndex) {
163+
var name = node.tagName
164+
var elementsBefore = elements
165+
var elementsByTypeBefore = own.call(types, name) ? types[name] : 0
197166

198-
return index
167+
count(name)
199168

200-
function index(node) {
201-
var type = node.tagName
169+
delayed.push(fn)
202170

203-
if (!type) {
204-
return 0
205-
}
171+
function fn() {
172+
// Before counting further elements:
173+
state.elementIndex = elementsBefore
174+
state.typeIndex = elementsByTypeBefore
206175

207-
index.elements++
176+
// After counting all elements.
177+
state.elementCount = elements
178+
state.typeCount = types[name]
208179

209-
if (!own.call(counts, type)) {
210-
counts[type] = 0
180+
add(node, childIndex)
211181
}
182+
}
212183

213-
// Note: ++ needs is intended to be postfixed!
214-
return counts[type]++
184+
function add(node, childIndex) {
185+
var exit = enter(state, node)
186+
state.iterator(query, node, childIndex, parent, state)
187+
exit()
215188
}
216189

217-
function count(node) {
218-
return own.call(counts, node.tagName) ? counts[node.tagName] : 0
190+
function count(name) {
191+
if (!own.call(types, name)) {
192+
types[name] = 0
193+
}
194+
195+
elements++
196+
types[name]++
219197
}
220198
}

test/all.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ test('all together now', function(t) {
1111
'dl > dt.foo:nth-of-type(odd)',
1212
u('root', [
1313
h('dl', [
14+
'\n ',
1415
h('dt.foo', 'Alpha'),
16+
'\n ',
1517
h('dd', 'Bravo'),
18+
'\n ',
1619
h('dt', 'Charlie'),
20+
'\n ',
1721
h('dd', 'Delta'),
22+
'\n ',
1823
h('dt', 'Echo'),
19-
h('dd', 'Foxtrot')
24+
'\n ',
25+
h('dd', 'Foxtrot'),
26+
'\n'
2027
])
2128
])
2229
),
@@ -28,14 +35,23 @@ test('all together now', function(t) {
2835
'.foo ~ dd:nth-of-type(even)',
2936
u('root', [
3037
h('dl', [
38+
'\n ',
3139
h('dt', 'Alpha'),
40+
'\n ',
3241
h('dd', 'Bravo'),
42+
'\n ',
3343
h('dt.foo', 'Charlie'),
44+
'\n ',
3445
h('dd', 'Delta'),
46+
'\n ',
3547
h('dt', 'Echo'),
48+
'\n ',
3649
h('dd', 'Foxtrot'),
50+
'\n ',
3751
h('dt', 'Golf'),
38-
h('dd', 'Hotel')
52+
'\n ',
53+
h('dd', 'Hotel'),
54+
'\n'
3955
])
4056
])
4157
),
@@ -47,14 +63,23 @@ test('all together now', function(t) {
4763
'.foo + dd:nth-of-type(even)',
4864
u('root', [
4965
h('dl', [
66+
'\n ',
5067
h('dt', 'Alpha'),
68+
'\n ',
5169
h('dd', 'Bravo'),
70+
'\n ',
5271
h('dt.foo', 'Charlie'),
72+
'\n ',
5373
h('dd', 'Delta'),
74+
'\n ',
5475
h('dt', 'Echo'),
76+
'\n ',
5577
h('dd', 'Foxtrot'),
78+
'\n ',
5679
h('dt', 'Golf'),
57-
h('dd', 'Hotel')
80+
'\n ',
81+
h('dd', 'Hotel'),
82+
'\n'
5883
])
5984
])
6085
),

test/select-all.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,27 @@ test('select.selectAll()', function(t) {
183183
'should return next-sibling'
184184
)
185185

186+
st.deepEqual(
187+
selectAll(
188+
'a + b',
189+
u('root', [
190+
u('text', '\n'),
191+
h('a', 'Lorem'),
192+
u('text', ' ipsum '),
193+
h('b', 'dolor'),
194+
u('text', ' sit '),
195+
h('i', 'amet'),
196+
u('text', ' sed '),
197+
h('b', 'do'),
198+
u('text', ' eiusmod '),
199+
h('i', 'tempor'),
200+
u('text', '.\n')
201+
])
202+
),
203+
[h('b', 'dolor')],
204+
'should return next-sibling ignoring non-elements'
205+
)
206+
186207
st.deepEqual(
187208
selectAll(
188209
'h1 + p',
@@ -230,6 +251,27 @@ test('select.selectAll()', function(t) {
230251
'should return subsequent siblings'
231252
)
232253

254+
st.deepEqual(
255+
selectAll(
256+
'a ~ i',
257+
u('root', [
258+
u('text', '\n'),
259+
h('a', 'Lorem'),
260+
u('text', ' ipsum '),
261+
h('b', 'dolor'),
262+
u('text', ' sit '),
263+
h('i', 'amet'),
264+
u('text', ' sed '),
265+
h('b', 'do'),
266+
u('text', ' eiusmod '),
267+
h('i', 'tempor'),
268+
u('text', '.\n')
269+
])
270+
),
271+
[h('i', 'amet'), h('i', 'tempor')],
272+
'should return siblings ignoring non-elements'
273+
)
274+
233275
st.deepEqual(
234276
selectAll(
235277
'h1 ~ p',

0 commit comments

Comments
 (0)