Skip to content

Commit bb7860c

Browse files
committed
fix: support capturing only name of type parameters
1 parent 46df5be commit bb7860c

13 files changed

+291
-127
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
"resolve": "^1.19.0",
221221
"shallowequal": "^1.1.0",
222222
"tiny-typed-emitter": "^2.1.0",
223-
"ts-node": "^9.1.1",
223+
"ts-node": "^10.9.1",
224224
"typed-validators": "^4.5.1",
225225
"yargs": "^17.6.2"
226226
}

pnpm-lock.yaml

Lines changed: 68 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compileMatcher/Identifier.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Identifier, NodePath } from '../types'
1+
import { Identifier, NodePath, setAstxMatchInfo } from '../types'
22
import compileMatcher, { CompiledMatcher, CompileOptions, MatchResult } from '.'
33
import compilePlaceholderMatcher, { unescapeIdentifier } from './Placeholder'
44

@@ -71,9 +71,12 @@ export default function compileIdentifierMatcher(
7171
: null
7272

7373
if (captured) {
74-
;(captured.node as any).astx = {
75-
excludeTypeAnnotationFromCapture: true,
76-
}
74+
setAstxMatchInfo(captured.node, {
75+
subcapture: {
76+
type: 'Identifier',
77+
name: (captured.node as Identifier).name,
78+
},
79+
})
7780
}
7881

7982
return typeAnnotationMatcher.match(

src/compileMatcher/TSTypeParameter.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,53 @@
1-
import { TSTypeParameter, NodePath } from '../types'
2-
import { CompiledMatcher, CompileOptions } from '.'
1+
import { TSTypeParameter, NodePath, setAstxMatchInfo } from '../types'
2+
import { CompiledMatcher, CompileOptions, MatchResult } from '.'
33
import compilePlaceholderMatcher from './Placeholder'
4+
import compileGenericNodeMatcher from './GenericNodeMatcher'
45

56
export default function compileTSTypeParameterMatcher(
67
path: NodePath<TSTypeParameter, TSTypeParameter>,
78
compileOptions: CompileOptions
89
): CompiledMatcher | void {
910
const pattern: TSTypeParameter = path.value
1011

11-
if (pattern.constraint == null && pattern.default == null) {
12-
const placeholderMatcher = compilePlaceholderMatcher(
13-
path,
14-
pattern.name,
15-
compileOptions,
16-
{ nodeType: 'TSTypeParameter' }
17-
)
12+
const placeholderMatcher = compilePlaceholderMatcher(
13+
path,
14+
pattern.name,
15+
compileOptions,
16+
{ nodeType: 'TSTypeParameter' }
17+
)
1818

19-
if (placeholderMatcher) return placeholderMatcher
19+
if (placeholderMatcher) {
20+
if (pattern.constraint == null && pattern.default == null) {
21+
return placeholderMatcher
22+
}
23+
24+
const { placeholder } = placeholderMatcher
25+
26+
const genericMatcher = compileGenericNodeMatcher(path, compileOptions, {
27+
keyMatchers: { name: placeholderMatcher },
28+
})
29+
30+
return {
31+
...genericMatcher,
32+
33+
match: (path: NodePath, matchSoFar: MatchResult): MatchResult => {
34+
matchSoFar = genericMatcher.match(path, matchSoFar)
35+
36+
if (matchSoFar == null) return null
37+
38+
const captured = placeholder ? matchSoFar.captures?.[placeholder] : null
39+
40+
if (captured) {
41+
setAstxMatchInfo(captured.node, {
42+
subcapture: {
43+
type: 'TSTypeParameter',
44+
name: (captured.node as TSTypeParameter).name,
45+
},
46+
})
47+
}
48+
49+
return matchSoFar
50+
},
51+
}
2052
}
2153
}

src/compileMatcher/TypeParameter.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,53 @@
1-
import { TypeParameter, NodePath } from '../types'
2-
import { CompiledMatcher, CompileOptions } from '.'
1+
import { TypeParameter, NodePath, setAstxMatchInfo } from '../types'
2+
import { CompiledMatcher, CompileOptions, MatchResult } from '.'
33
import compilePlaceholderMatcher from './Placeholder'
4+
import compileGenericNodeMatcher from './GenericNodeMatcher'
45

56
export default function compileTypeParameterMatcher(
67
path: NodePath<TypeParameter, TypeParameter>,
78
compileOptions: CompileOptions
89
): CompiledMatcher | void {
910
const pattern: TypeParameter = path.value
1011

11-
if (pattern.variance == null && pattern.bound == null) {
12-
const placeholderMatcher = compilePlaceholderMatcher(
13-
path,
14-
pattern.name,
15-
compileOptions,
16-
{ nodeType: 'TypeParameter' }
17-
)
12+
const placeholderMatcher = compilePlaceholderMatcher(
13+
path,
14+
pattern.name,
15+
compileOptions,
16+
{ nodeType: 'TypeParameter' }
17+
)
1818

19-
if (placeholderMatcher) return placeholderMatcher
19+
if (placeholderMatcher) {
20+
if (pattern.variance == null && pattern.bound == null) {
21+
return placeholderMatcher
22+
}
23+
24+
const { placeholder } = placeholderMatcher
25+
26+
const genericMatcher = compileGenericNodeMatcher(path, compileOptions, {
27+
keyMatchers: { name: placeholderMatcher },
28+
})
29+
30+
return {
31+
...genericMatcher,
32+
33+
match: (path: NodePath, matchSoFar: MatchResult): MatchResult => {
34+
matchSoFar = genericMatcher.match(path, matchSoFar)
35+
36+
if (matchSoFar == null) return null
37+
38+
const captured = placeholder ? matchSoFar.captures?.[placeholder] : null
39+
40+
if (captured) {
41+
setAstxMatchInfo(captured.node, {
42+
subcapture: {
43+
type: 'TypeParameter',
44+
name: (captured.node as TypeParameter).name,
45+
},
46+
})
47+
}
48+
49+
return matchSoFar
50+
},
51+
}
2052
}
2153
}

src/compileReplacement/Placeholder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
CompiledReplacement,
44
ReplaceableMatch,
55
} from '.'
6-
import { Node, NodePath } from '../types'
6+
import { Node, NodePath, getAstxMatchInfo } from '../types'
77
import {
88
getArrayPlaceholder,
99
getPlaceholder,
@@ -55,8 +55,8 @@ export default function compilePlaceholderReplacement(
5555
const capture = match.captures?.[placeholder]
5656
if (capture) {
5757
const clone = cloneNode(capture)
58-
if ((capture as any).astx?.excludeTypeAnnotationFromCapture)
59-
delete (clone as any).typeAnnotation
58+
const astx = getAstxMatchInfo(capture)
59+
if (astx?.subcapture) return convertReplacement(astx.subcapture)
6060
return convertReplacement(clone)
6161
}
6262
return convertReplacement(cloneNode(pattern.value))

src/types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,30 @@ import * as k from 'ast-types/gen/kinds'
44

55
type ArrayElement<A> = A extends readonly (infer T)[] ? T : never
66

7+
export type AstxMatchInfo = {
8+
/**
9+
* Used for cases like when capturing only the name of an identifier with a type annotation
10+
* like `$a: number`. The name property isn't a node, so we have to capture the Identifier
11+
* node, which includes the `number` TypeAnnotation. But we don't want the TypeAnnotation
12+
* to be included when interpolating the capture in replcements, so we set a `subcapture`
13+
* that's a copy of the Identifier without the TypeAnnotation, and when replacing, we use
14+
* any `subcapture` in place of the captured node.
15+
*/
16+
subcapture?: Node
17+
}
18+
19+
export function getAstxMatchInfo(node: Node): AstxMatchInfo | undefined {
20+
return (node as any).astx
21+
}
22+
23+
export function setAstxMatchInfo(
24+
node: Node,
25+
info: AstxMatchInfo | undefined
26+
): void {
27+
if (!info) delete (node as any).astx
28+
else (node as any).astx = info
29+
}
30+
731
export type Location = {
832
start?: number | null
933
end?: number | null

0 commit comments

Comments
 (0)