Skip to content

Commit 02b9fdd

Browse files
committed
feat(parse): support extracting node between two empty comments
1 parent 2eb4d60 commit 02b9fdd

File tree

11 files changed

+319
-71
lines changed

11 files changed

+319
-71
lines changed
Lines changed: 76 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,77 @@
11
{
2-
"findTestcase": {
3-
"prefix": "find-testcase",
4-
"body": [
5-
"export const input = `",
6-
"$0",
7-
"`",
8-
"",
9-
"export const find = `",
10-
"",
11-
"`",
12-
"",
13-
"export const expectedFind = [",
14-
"",
15-
"]",
16-
""
17-
]
18-
},
19-
"findReplaceTestcase": {
20-
"prefix": "find-replace-testcase",
21-
"body": [
22-
"export const input = `",
23-
"$0",
24-
"`",
25-
"",
26-
"export const find = `",
27-
"",
28-
"`",
29-
"",
30-
"export const expectedFind = [",
31-
"",
32-
"]",
33-
"",
34-
"export const replace = `",
35-
"",
36-
"`",
37-
"",
38-
"export const expectedReplace = `",
39-
"",
40-
"`",
41-
""
42-
]
43-
},
44-
"replaceTestcase": {
45-
"prefix": "replace-testcase",
46-
"body": [
47-
"export const input = `",
48-
"$0",
49-
"`",
50-
"",
51-
"export const find = `",
52-
"",
53-
"`",
54-
"",
55-
"export const replace = `",
56-
"",
57-
"`",
58-
"",
59-
"export const expectedReplace = `",
60-
"",
61-
"`",
62-
""
63-
]
64-
}
65-
}
2+
"parseTestcase": {
3+
"prefix": "parse-testcase",
4+
"body": [
5+
"export const input = `",
6+
"$0",
7+
"`",
8+
"",
9+
"export const expected = `",
10+
"",
11+
"`"
12+
]
13+
},
14+
"findTestcase": {
15+
"prefix": "find-testcase",
16+
"body": [
17+
"export const input = `",
18+
"$0",
19+
"`",
20+
"",
21+
"export const find = `",
22+
"",
23+
"`",
24+
"",
25+
"export const expectedFind = [",
26+
"",
27+
"]",
28+
""
29+
]
30+
},
31+
"findReplaceTestcase": {
32+
"prefix": "find-replace-testcase",
33+
"body": [
34+
"export const input = `",
35+
"$0",
36+
"`",
37+
"",
38+
"export const find = `",
39+
"",
40+
"`",
41+
"",
42+
"export const expectedFind = [",
43+
"",
44+
"]",
45+
"",
46+
"export const replace = `",
47+
"",
48+
"`",
49+
"",
50+
"export const expectedReplace = `",
51+
"",
52+
"`",
53+
""
54+
]
55+
},
56+
"replaceTestcase": {
57+
"prefix": "replace-testcase",
58+
"body": [
59+
"export const input = `",
60+
"$0",
61+
"`",
62+
"",
63+
"export const find = `",
64+
"",
65+
"`",
66+
"",
67+
"export const replace = `",
68+
"",
69+
"`",
70+
"",
71+
"export const expectedReplace = `",
72+
"",
73+
"`",
74+
""
75+
]
76+
}
77+
}

src/babel/BabelBackend.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as defaultTypes from '@babel/types'
66
import defaultGenerate from '@babel/generator'
77
import * as AstTypes from 'ast-types'
88
import babelAstTypes from './babelAstTypes'
9-
import { Location } from '../types'
9+
import { Comment, Location } from '../types'
1010

1111
interface Parser {
1212
parse(code: string, parserOpts?: ParserOptions): File
@@ -22,6 +22,10 @@ export default class BabelBackend extends Backend<Node> {
2222
readonly parseStatements: (code: string) => Statement[]
2323
readonly generate: Generate
2424
readonly location: (node: Node) => Location
25+
readonly comments: (
26+
node: Node,
27+
kind?: 'leading' | 'inner' | 'trailing'
28+
) => Iterable<Comment>
2529

2630
constructor({
2731
parser = defaultParser,
@@ -58,5 +62,22 @@ export default class BabelBackend extends Backend<Node> {
5862
endLine: node.loc?.end?.line,
5963
endColumn: node.loc?.end?.column,
6064
})
65+
this.comments = function* comments(
66+
node: Node,
67+
kind?: 'leading' | 'inner' | 'trailing'
68+
): Iterable<Comment> {
69+
if (!kind || kind === 'leading') {
70+
const { leadingComments } = node
71+
if (leadingComments) yield* leadingComments
72+
}
73+
if (!kind || kind === 'inner') {
74+
const { innerComments } = node
75+
if (innerComments) yield* innerComments
76+
}
77+
if (!kind || kind === 'trailing') {
78+
const { trailingComments } = node
79+
if (trailingComments) yield* trailingComments
80+
}
81+
}
6182
}
6283
}

src/backend/Backend.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NodePath, Statement, Expression, Location } from '../types'
1+
import { NodePath, Statement, Expression, Location, Comment } from '../types'
22
import { parsePatternToNodes, parsePattern } from './parse'
33
import * as template from './template'
44
import * as AstTypes from 'ast-types'
@@ -38,6 +38,10 @@ export abstract class Backend<Node = any> {
3838

3939
abstract readonly generate: (node: Node) => { code: string }
4040
abstract readonly location: (node: Node) => Location
41+
abstract readonly comments: (
42+
node: Node,
43+
kind?: 'leading' | 'inner' | 'trailing'
44+
) => Iterable<Comment>
4145

4246
constructor() {
4347
this.template = {

src/backend/parse.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Backend } from './Backend'
2-
import { NodePath, Expression, Statement, Node } from '../types'
2+
import { NodePath, Expression, Statement, Node, Comment } from '../types'
33
import ensureArray from '../util/ensureArray'
44
import forEachNode from '../util/forEachNode'
55

@@ -30,8 +30,41 @@ export function parsePattern(
3030
let result: NodePath | NodePath[] = ensureArray(ast).map(
3131
(n) => new this.t.NodePath(n)
3232
)
33-
let extractNext = false
33+
const allComments: Comment[] = []
34+
forEachNode(this.t, result, ['Node'], (path: NodePath) => {
35+
if (allComments.length >= 2) return
36+
const { node } = path
37+
for (const comment of this.comments(node)) {
38+
allComments.push(comment)
39+
}
40+
})
3441
let done = false
42+
if (allComments.length >= 2) {
43+
let from = Infinity,
44+
to = -Infinity
45+
for (const comment of allComments) {
46+
const { start, end } = this.location(comment)
47+
if (start != null && start > to) to = start
48+
if (end != null && end < from) from = end
49+
}
50+
if (from != null && to != null && from < to) {
51+
const pathInRange = (path: NodePath) => {
52+
const { start, end } = this.location(path.node)
53+
return start != null && end != null && start >= from && end <= to
54+
}
55+
forEachNode(this.t, result, ['Node'], (path: NodePath) => {
56+
if (done) return
57+
if (pathInRange(path)) {
58+
while (path.parentPath != null && pathInRange(path.parentPath))
59+
path = path.parentPath
60+
result = path
61+
done = true
62+
}
63+
})
64+
if (done) return result
65+
}
66+
}
67+
let extractNext = false
3568
forEachNode(this.t, result, ['Node'], (path: NodePath) => {
3669
if (done) return
3770
if (extractNext) {

src/recast/RecastBackend.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
1-
import { File, Statement, Expression, Location } from '../types'
1+
import { File, Statement, Expression, Location, Comment } from '../types'
22
import { Backend } from '../backend/Backend'
33
import * as defaultRecast from 'recast'
44
import * as t from 'ast-types'
5+
import * as k from 'ast-types/gen/kinds'
56

6-
export default class RecastBackend<Node = any> extends Backend<Node> {
7+
type Node = k.NodeKind
8+
9+
export default class RecastBackend extends Backend<Node> {
710
readonly wrapped: Backend
811
readonly t: typeof t
912
readonly parse: (code: string) => Node
1013
readonly parseExpression: (code: string) => Expression
1114
readonly parseStatements: (code: string) => Statement[]
1215
readonly generate: (node: Node) => { code: string }
1316
readonly location: (node: Node) => Location
17+
readonly comments: (
18+
node: Node,
19+
kind?: 'leading' | 'trailing' | 'inner'
20+
) => Iterable<Comment>
1421

1522
constructor({
1623
wrapped,
1724
recast = defaultRecast,
1825
parseOptions,
1926
}: {
20-
wrapped: Backend<Node>
27+
wrapped: Backend
2128
recast?: typeof defaultRecast
2229
parseOptions?: defaultRecast.Options
2330
}) {
@@ -56,5 +63,18 @@ export default class RecastBackend<Node = any> extends Backend<Node> {
5663
}
5764
this.generate = (node: Node): { code: string } => recast.print(node as any)
5865
this.location = wrapped.location
66+
this.comments = function* comments(
67+
node: Node,
68+
kind?: 'leading' | 'inner' | 'trailing'
69+
): Iterable<Comment> {
70+
if (!node.comments) return
71+
yield* node.comments?.filter(
72+
(comment) =>
73+
!kind ||
74+
(kind === 'inner'
75+
? !comment.leading && !comment.trailing
76+
: comment[kind] === true)
77+
)
78+
}
5979
}
6080
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export type NodeType =
6464
| keyof b.Aliases
6565

6666
export type Node = b.Node | k.NodeKind
67+
export type Comment = b.Comment | k.CommentKind
6768

6869
export type File = b.File | k.FileKind
6970
export type Block =

0 commit comments

Comments
 (0)