Skip to content

Commit 925f9c5

Browse files
committed
feat: create pathsIntersectingRange
1 parent 777dee5 commit 925f9c5

3 files changed

Lines changed: 111 additions & 1 deletion

File tree

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,19 @@ j(editor.getText())
4242
)
4343
.forEach(path => console.log(path.node))
4444
```
45+
46+
### `pathsIntersectingRange(start: number, end: number = start)`
47+
48+
The only difference between this and `pathsInRange` is it will include nodes that
49+
extend beyond `start` or `end`.
50+
51+
```js
52+
import pathsIntersectingRange from 'jscodeshift-paths-in-range'
53+
```
54+
55+
Returns a predicate for `Collection.filter`.
56+
57+
#### Arguments
58+
59+
- `start` - the index of the start of the range within the source code
60+
- `end` - the index of the end of the range within the source code (defaults to `start`)

src/index.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { expect } from 'chai'
55
import pathsToTransformFilter from './index'
66
import jscodeshift from 'jscodeshift'
77

8+
import { pathsIntersectingRange } from './'
9+
810
const j = jscodeshift.withParser('babylon')
911

1012
describe(`pathsToTransformFilter`, function() {
@@ -108,3 +110,40 @@ var qlarmge
108110
)
109111
})
110112
})
113+
114+
describe(`pathsIntersectingRange`, function() {
115+
const code = `
116+
const Comp = () => (
117+
<div>
118+
This is a
119+
<button>
120+
Test
121+
</button>
122+
<span />
123+
</div>
124+
)
125+
`
126+
127+
it('works', () => {
128+
const root = j(code)
129+
const elements = root
130+
.find(j.Node)
131+
.filter(
132+
path =>
133+
path.node.type !== 'JSXOpeningElement' &&
134+
path.node.type !== 'JSXClosingElement' &&
135+
(path.node.type === 'JSXElement' ||
136+
(path.parent && path.parent.node.type === 'JSXElement'))
137+
)
138+
.filter(
139+
pathsIntersectingRange(code.indexOf('is a'), code.indexOf('<span'))
140+
)
141+
.nodes()
142+
expect(elements[0].type).to.equal('JSXText')
143+
expect((elements[0] as any).value).to.match(/is a/)
144+
expect(elements[1].type).to.equal('JSXElement')
145+
expect(elements[2].type).to.equal('JSXText')
146+
expect((elements[2] as any).value).not.to.match(/\S/)
147+
expect(elements).to.have.lengthOf(3)
148+
})
149+
})

src/index.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,62 @@ export default function pathsInRange(
3636
)
3737
}
3838

39-
// return the bottommost paths that fully contains the range
39+
// return the bottommost path that fully contains the range
40+
const allFullContainers = paths.filter(
41+
path =>
42+
(path.value as any).start <= start && (path.value as any).end >= end
43+
)
44+
const fullContainersSet = new Set(allFullContainers)
45+
allFullContainers.forEach(path => {
46+
for (const ancestor of ancestorsOfPath(path)) {
47+
fullContainersSet.delete(ancestor)
48+
}
49+
})
50+
return fullContainersSet
51+
}
52+
53+
return (
54+
path: ASTPath<Node>,
55+
index: number,
56+
paths: Array<ASTPath<Node>>
57+
): boolean => {
58+
if (paths !== lastPaths) {
59+
lastPaths = paths
60+
lastPathSet = computePathSet(paths)
61+
}
62+
return lastPathSet.has(path)
63+
}
64+
}
65+
66+
export function pathsIntersectingRange(
67+
start: number,
68+
end: number = start
69+
): (path: ASTPath<Node>, i: number, paths: Array<ASTPath<Node>>) => boolean {
70+
let lastPaths: Array<ASTPath<Node>> | null = null
71+
let lastPathSet: Set<ASTPath<Node>> = new Set()
72+
73+
function computePathSet(paths: Array<ASTPath<Node>>): Set<ASTPath<Node>> {
74+
const intersected = paths.filter((path: ASTPath<Node>): boolean => {
75+
const value: any = path.value
76+
return (
77+
value.start < end &&
78+
value.end > start &&
79+
(start <= value.start || end >= value.end)
80+
)
81+
})
82+
if (intersected.length) {
83+
const intersectedSet = new Set(intersected)
84+
return new Set(
85+
intersected.filter(path => {
86+
for (const ancestor of ancestorsOfPath(path)) {
87+
if (intersectedSet.has(ancestor)) return false
88+
}
89+
return true
90+
})
91+
)
92+
}
93+
94+
// return the bottommost path that fully contains the range
4095
const allFullContainers = paths.filter(
4196
path =>
4297
(path.value as any).start <= start && (path.value as any).end >= end

0 commit comments

Comments
 (0)