Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace a path in AST with just parsed javascript(string)?

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")
like image 673
user2167582 Avatar asked Apr 23 '18 21:04

user2167582


2 Answers

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.

like image 51
bgran Avatar answered Jan 03 '23 16:01

bgran


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.

like image 34
Ira Baxter Avatar answered Jan 03 '23 17:01

Ira Baxter