Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser interprets it as a CallExpression (positional) not a CallExpressionKw. But then if a codemod wants to add a tag to it, it tries adding a kwarg called 'tag', which fails because the CallExpression doesn't need kwargs inserted into it. The fix is: change the node from CallExpression to CallExpressionKw, and update getNodeFromPath to take a 'replacement' arg, so we can replace the old node with the new node in the AST.
This commit is contained in:
committed by
Nick Cameron
parent
cd022fb087
commit
99866b5f9a
@ -51,7 +51,8 @@ export function getNodeFromPath<T>(
|
|||||||
node: Program,
|
node: Program,
|
||||||
path: PathToNode,
|
path: PathToNode,
|
||||||
stopAt?: SyntaxType | SyntaxType[],
|
stopAt?: SyntaxType | SyntaxType[],
|
||||||
returnEarly = false
|
returnEarly = false,
|
||||||
|
replacement?: any
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
node: T
|
node: T
|
||||||
@ -63,9 +64,14 @@ export function getNodeFromPath<T>(
|
|||||||
let stopAtNode = null
|
let stopAtNode = null
|
||||||
let successfulPaths: PathToNode = []
|
let successfulPaths: PathToNode = []
|
||||||
let pathsExplored: PathToNode = []
|
let pathsExplored: PathToNode = []
|
||||||
|
let parent = null as any
|
||||||
|
let parentEdge = null
|
||||||
for (const pathItem of path) {
|
for (const pathItem of path) {
|
||||||
if (typeof currentNode[pathItem[0]] !== 'object') {
|
if (typeof currentNode[pathItem[0]] !== 'object') {
|
||||||
if (stopAtNode) {
|
if (stopAtNode) {
|
||||||
|
if (replacement && parent && parentEdge) {
|
||||||
|
parent[parentEdge] = replacement
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
node: stopAtNode,
|
node: stopAtNode,
|
||||||
shallowPath: pathsExplored,
|
shallowPath: pathsExplored,
|
||||||
@ -74,6 +80,8 @@ export function getNodeFromPath<T>(
|
|||||||
}
|
}
|
||||||
return new Error('not an object')
|
return new Error('not an object')
|
||||||
}
|
}
|
||||||
|
parent = currentNode
|
||||||
|
parentEdge = pathItem[0]
|
||||||
currentNode = currentNode?.[pathItem[0]]
|
currentNode = currentNode?.[pathItem[0]]
|
||||||
successfulPaths.push(pathItem)
|
successfulPaths.push(pathItem)
|
||||||
if (!stopAtNode) {
|
if (!stopAtNode) {
|
||||||
@ -97,6 +105,9 @@ export function getNodeFromPath<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (replacement && parent && parentEdge) {
|
||||||
|
parent[parentEdge] = replacement
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
node: stopAtNode || currentNode,
|
node: stopAtNode || currentNode,
|
||||||
shallowPath: pathsExplored,
|
shallowPath: pathsExplored,
|
||||||
|
|||||||
@ -2466,15 +2466,30 @@ function addTag(tagIndex = 2): addTagFn {
|
|||||||
function addTagKw(): addTagFn {
|
function addTagKw(): addTagFn {
|
||||||
return ({ node, pathToNode }) => {
|
return ({ node, pathToNode }) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const callExpr = getNodeFromPath<Node<CallExpressionKw>>(
|
// We have to allow for the possibility that the path is actually to a call expression.
|
||||||
|
// That's because if the parser reads something like `close()`, it doesn't know if this
|
||||||
|
// is a keyword or positional call.
|
||||||
|
// In fact, even something like `close(%)` could be either (because we allow 1 unlabeled
|
||||||
|
// starting param).
|
||||||
|
const callExpr = getNodeFromPath<Node<CallExpressionKw | CallExpression>>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'CallExpressionKw'
|
['CallExpressionKw', 'CallExpression']
|
||||||
)
|
)
|
||||||
if (err(callExpr)) return callExpr
|
if (err(callExpr)) return callExpr
|
||||||
|
|
||||||
const { node: primaryCallExp } = callExpr
|
// If the original node is a call expression, we'll need to change it to a call with keyword args.
|
||||||
|
const primaryCallExp: CallExpressionKw =
|
||||||
|
callExpr.node.type === 'CallExpressionKw'
|
||||||
|
? callExpr.node
|
||||||
|
: {
|
||||||
|
type: 'CallExpressionKw',
|
||||||
|
callee: callExpr.node.callee,
|
||||||
|
unlabeled: callExpr.node.arguments.length
|
||||||
|
? callExpr.node.arguments[0]
|
||||||
|
: null,
|
||||||
|
arguments: [],
|
||||||
|
}
|
||||||
const tagArg = findKwArg(ARG_TAG, primaryCallExp)
|
const tagArg = findKwArg(ARG_TAG, primaryCallExp)
|
||||||
const tagDeclarator =
|
const tagDeclarator =
|
||||||
tagArg || createTagDeclarator(findUniqueName(_node, 'seg', 2))
|
tagArg || createTagDeclarator(findUniqueName(_node, 'seg', 2))
|
||||||
@ -2483,6 +2498,17 @@ function addTagKw(): addTagFn {
|
|||||||
const labeledArg = createLabeledArg(ARG_TAG, tagDeclarator)
|
const labeledArg = createLabeledArg(ARG_TAG, tagDeclarator)
|
||||||
primaryCallExp.arguments.push(labeledArg)
|
primaryCallExp.arguments.push(labeledArg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we changed the node, we must replace the old node with the new node in the AST.
|
||||||
|
const mustReplaceNode = primaryCallExp.type !== callExpr.node.type
|
||||||
|
if (mustReplaceNode) {
|
||||||
|
getNodeFromPath(_node, pathToNode, ['CallExpression'], false, {
|
||||||
|
...primaryCallExp,
|
||||||
|
start: callExpr.node.start,
|
||||||
|
end: callExpr.node.end,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if ('value' in tagDeclarator) {
|
if ('value' in tagDeclarator) {
|
||||||
// Now TypeScript knows tagDeclarator has a value property
|
// Now TypeScript knows tagDeclarator has a value property
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user