https://astexplorer.net/#/gist/70df1bc56b9ee73d19fc949d2ef829ed/7e14217fd8510f0bf83f3372bf08454b7617bce1
I've found now I'm trying to replace
an expression and I don't care whats in it.
in this example I've found the this.state.showMenu && this.handleMouseDown
portion in
<a
onMouseDown={this.state.showMenu && this.handleMouseDown}
>
I need to convert to:
<a
onMouseDown={this.state.showMenu ? this.handleMouseDown : undefined}
>
how can I do so without explicitly reconstructing the tree? I just want to do something like
path.replaceText("this.state.showMenu ? this.handleMouseDown : undefined")
Here's a transformer that does what you describe:
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source)
root
.find(j.JSXExpressionContainer)
.replaceWith(path => {
return j.jsxExpressionContainer(
j.conditionalExpression(
j.identifier(j(path.value.expression.left).toSource()),
j.identifier(j(path.value.expression.right).toSource()),
j.identifier('undefined')
)
)
})
return root.toSource()
}
See it in action here.
You can also just put arbitrary text in the JSXExpressionContainer
node:
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source)
root
.find(j.JSXExpressionContainer)
.replaceWith(path => {
return j.jsxExpressionContainer(
j.identifier('whatever you want')
)
})
return root.toSource()
}
See this example.
Finally, you don't even need to return a JSXExpressionContainer
.
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source)
root
.find(j.JSXExpressionContainer)
.replaceWith(path => {
return j.identifier("this isn't valid JS, but works just fine")
})
return root.toSource()
}
See the result here.
You can do this with our DMS Software Reengineering Toolkit.
DMS treats HTML pages as native HTML text with embedded scripting sublanguage, which might be ECMAScript, or VBScript, or something else. So the process of building a complete HTML "AST" requires that one first build the pure HTML part, then find all the "onXXXXX" tags and convert them to ASTs in the chosen scripting language. DMS can distinguish AST nodes from different langauges so there's no chance of confusion in understanding the compound AST.
So, first we need to parse the HTML document of interest (code edited for pedagogical reasons):
(local (;; [my_HTML_AST AST:Node]
(includeunique `DMS/Domains/HTML/Component/ParserComponent.par')
);;
(= working_graph (AST:CreateForest))
(= my_HTML_AST (Parser:ParseFile parser working_graph input_file_full_path))
Then we need to walk over the HTML tree, find the JavaScript text fragments, parse them and splice the parsed ECMASCript tree in to replace the text fragment:
(local (;; (includeunique `DMS/Domains/ECMAScript/Components/ParserComponent.par') );;
(ScanNodes my_HTML_AST
(lambda (function boolean AST:Node)
(ifthenelse (!! (~= (AST:GetNodeType ?) GrammarConstants:Rule:Attribute) ; not an HTML attribute
(~= (Strings:Prefix (AST:GetLiteralString (AST:GetFirstChild ?)) `on')) ; not at action attribute
)&&
~t ; scan deeper into tree
(value (local (;; [my_ECMAScript_AST AST:Node]
[ECMASCript_text_stream streams:buffer]
);;
(= ECMAScript_text_stream (InputStream:MakeBufferStream (AST:StringLiteral (AST:GetSecondChild ?))=
(= my_ECMAScript_AST (Parser:ParseStream parser working_graph ECMAScript_text_stream))
(= AST:ReplaceNode ? my_ECMAScript_AST)
(= InputStream:Close my_ECMAScript_text_stream)
~f) ; no need to scan deeper here
)ifthenelse
)lambda
) ; at this point, we have a mixed HTML/ECMAScript tree
)local
If the scripting language can be something else, then this code has to change. If your pages are all HTML + ECMAScript, you can wrap the above stuff into a black box and call it "(ParseHTML)" which is what the other answer assumed happened.
Now for the actual work. OP want to replace a pattern found in his HTML with another. Here DMS shines because you can write those patterns, using the syntax of the targeted language, directly as a DMS Rewrite Rule (see this link for details).
source domain ECMAScript;
target domain ECMAScript;
rule OP_special_rewrite()=expression -> expression
"this.state.showMenu && this.handleMouseDown"
-> "this.state.showMenu ? this.handleMouseDown : undefined "
Now you need to apply this rewrite:
(RSL:Apply my_HTML_AST `OP_special_rewrite') ; applies this rule to every node in AST
; only those that match get modified
And finally regenerate text from the AST:
(PrettyPrinter:PrintStream my_ECMAScript_AST input_file_full_path)
OP's example is pretty simply because he is matching against what amounts to a constant pattern. DMS's rules can be written using all kinds of pattern variables; see above link, and can have arbitrary conditions over the matched pattern and other state information to control whether the rule applies.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With