Skip to content

Commit

Permalink
Warn about reserved names
Browse files Browse the repository at this point in the history
As mentioned in #10, you cannot use a TwiML verb name as a parameter in
a function definition, like this:

```elixir
Enum.each [1, 2] fn(number) ->
  say "Press #{number}"
end
```

This commit adds a nice warning message if a user attempts to do this,
telling them that the "number" verb is reserved.
  • Loading branch information
danielberkompas committed Dec 14, 2015
1 parent 07f42b3 commit c138636
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 2 deletions.
29 changes: 27 additions & 2 deletions lib/ex_twiml.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ defmodule ExTwiml do

import ExTwiml.Utilities

alias ExTwiml.ReservedNameError

@verbs [
# Nested
:gather, :dial, :message,
Expand Down Expand Up @@ -99,15 +101,17 @@ defmodule ExTwiml do
# The buffer's state is a list of XML fragments. New fragments are
# inserted by other macros. Finally, all the fragments are joined
# together in a string.
{:ok, var!(buffer, Twiml)} = start_buffer([header])
{:ok, var!(buffer, Twiml)} = start_buffer([header])

# Wrap the whole block in a <Response> tag
tag :response do
# Postwalk the AST, expanding all of the TwiML verbs into proper
# `tag` and `text` macros. This gives the impression that there
# is a macro for each verb, when in fact it all expands to only
# two macros.
unquote(Macro.postwalk(block, &postwalk/1))
unquote(block
|> Macro.prewalk(&prewalk(&1, __CALLER__.file))
|> Macro.postwalk(&postwalk/1))
end

xml = render(var!(buffer, Twiml)) # Convert buffer to string
Expand Down Expand Up @@ -174,6 +178,14 @@ defmodule ExTwiml do
# Private API
##

# Check function definitions for reserved variable names
defp prewalk({:fn, _, [{:-> , _, [[vars], _]}]} = ast, file_name) do
assert_no_verbs!(vars, file_name)
ast
end

defp prewalk(ast, _file_name), do: ast

# {:text, [], ["Hello World"]}
defp postwalk({:text, _meta, [string]}) do
# Just add the text to the buffer. Nothing else needed.
Expand Down Expand Up @@ -239,4 +251,17 @@ defmodule ExTwiml do
put_buffer var!(buffer, Twiml), create_tag(:self_closed, unquote(verb), unquote(options))
end
end

defp assert_no_verbs!({name, _, _} = var, file_name)
when is_atom(name) and name in @verbs do
raise ReservedNameError, [var, file_name]
end

defp assert_no_verbs!(vars, file_name) when is_tuple(elem(vars, 0)) do
vars
|> Tuple.to_list
|> Enum.each(&assert_no_verbs!(&1, file_name))
end

defp assert_no_verbs!(vars, _file_name), do: vars
end
33 changes: 33 additions & 0 deletions lib/ex_twiml/reserved_name_error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule ExTwiml.ReservedNameError do
@moduledoc """
This error is thrown if you try to use TwiML verb name as a variable name
in your `twiml` block.
## Example
This code will raise the error, because `number` is a reserved name.
twiml do
Enum.each [1, 2], fn(number) ->
# ...
end
end
"""

defexception [:message]

@doc false
def exception([{name, context, _}, file_name]) do
file_name = Path.relative_to_cwd(file_name)
name = to_string(name)

message = ~s"""
"#{name}" is a reserved name in #{file_name}:#{context[:line]}, because it
is used to generate the <#{String.capitalize(name)} /> TwiML verb.
Please use a different variable name.
"""

%__MODULE__{message: message}
end
end
17 changes: 17 additions & 0 deletions test/ex_twiml/reserved_name_error_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule ExTwiml.ReservedNameErrorTest do
use ExUnit.Case

alias ExTwiml.ReservedNameError

test ".exception returns a nice exception" do
%{message: message} = ReservedNameError.exception([{:number, [line: 1], []},
"test/test.ex"])

assert message == ~s"""
"number" is a reserved name in test/test.ex:1, because it
is used to generate the <Number /> TwiML verb.
Please use a different variable name.
"""
end
end
18 changes: 18 additions & 0 deletions test/ex_twiml_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
defmodule ExTwimlTest do
use ExUnit.Case, async: false

import ExTwiml

alias ExTwiml.ReservedNameError

doctest ExTwiml

test "can render the <Gather> verb" do
Expand Down Expand Up @@ -226,6 +229,21 @@ defmodule ExTwimlTest do
assert_twiml markup, "<Say>123</Say>"
end

test ".twiml warns of reserved variable names" do
ast = quote do
twiml do
Enum.each [1, 2], fn(number) ->
say "#{number}"
end
end
end

assert_raise ReservedNameError, fn ->
# Simulate compiling the macro
Macro.expand(ast, __ENV__)
end
end

defp assert_twiml(lhs, rhs) do
assert lhs == "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response>#{rhs}</Response>"
end
Expand Down

0 comments on commit c138636

Please sign in to comment.