elixir-lang / tree-sitter-elixir

Elixir grammar for tree-sitter
https://elixir-lang.org/tree-sitter-elixir
Apache License 2.0
245 stars 24 forks source link

Advice on tests for matching and replacing code for `ast-grep`? #63

Closed dimitarvp closed 9 months ago

dimitarvp commented 9 months ago

Hello, Recently I've opened a PR in ast-grep: https://github.com/ast-grep/ast-grep/pull/749, that adds Elixir support to the tool so you can search and replace code from the command line. I've confirmed that it works for the several cases that I had in mind.

I can't open a discussion in this repo so I am opening an issue asking for advice on what kinds of matching and replacing would be good relevant tests for this tool? (It uses tree-sitter and tree-sitter-elixir.)

Example of a replace test I just added there is below. I am not asking you to be an expert in ast-grep's syntax, I am merely asking if you can give me ideas on less bare and more believable match / replace tests. I'll translate those to ast-grep rules.

Link to the Rust file exercising the Elixir tests: https://github.com/ast-grep/ast-grep/pull/749/files#diff-0ea893cc4bb08a539d62faa2f16dc54b322a191b18a5462f875f8731c47c6259

Input code:

Stream.map([1, 2, 3], fn x -> x * 2 end)

Search pattern:

Stream.map($$$ARGS)

Replacer:

Enum.map($$$ARGS)

Expected output:

Enum.map([1, 2, 3], fn x -> x * 2 end)
jonatanklosko commented 9 months ago

A few random ideas:

$LEFT = hd($RIGHT) -> [$LEFT | _] = $RIGHT

opts[$KEY] || $DEFAULT -> Keyword.get(opts, $KEY, $DEFAULT)

Mod.fun($ARG1, $ARG2) -> Mod.fun($ARG2, $ARG1)

Mod.fun($ARG1, $ARG2) -> Mod.fun($ARG1, option: $ARG2)

dimitarvp commented 9 months ago

Thank you! I just added those to the PR.

Maybe one more, with partial rewriting of a function's body from one idiom to another? šŸ¤”

dimitarvp commented 9 months ago

Here's what I came up with (and the test works):

Input code:

for x <- ["budgie", "cat", "dog"], do: String.to_atom(x)

Search pattern:

for $I <- $LIST, do: $MODULE.$FUNCTION($I)

Replacer:

Enum.map($LIST, fn $I -> $MODULE.$FUNCTION($I) end)

Expected output:

Enum.map(["budgie", "cat", "dog"], fn x -> String.to_atom(x) end)

I think all that is quite sufficient.

@jonatanklosko Feel free to close this as completed anytime you wish. Thank you for the ideas, they were useful and have been coded.

jonatanklosko commented 9 months ago

Perhaps Mod.fun($DATA, fn $ARG -> $MOD.$FUN($ARG) end) -> Mod.fun($DATA, &$MOD.$FUN/1) :)

dimitarvp commented 9 months ago

Yeah, thought about it and even tried it, worked, but figured it's superfluous as it was very similar to the previous one.

And looking through the tests of the other languages, Elixir will have much better coverage than many. šŸ˜