Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn about reserved names #11

Merged
merged 2 commits into from
Dec 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
language: elixir
elixir:
- 1.0.3
- 1.0.4
- 1.1.1
otp_release:
- 17.4
- 18.0
before_script:
- export PATH=`pwd`/elixir/bin:$PATH
- export PLT_FILENAME=elixir-${TRAVIS_ELIXIR_VERSION}_$TRAVIS_OTP_RELEASE.plt
- export PLT_LOCATION=/home/travis/$PLT_FILENAME
- wget -O $PLT_LOCATION https://raw.github.com/danielberkompas/travis_elixir_plts/master/$PLT_FILENAME
- mix local.hex --force
- mix deps.get --only test
script:
- mix test
- dialyzer --no_check_plt --plt $PLT_LOCATION -Wno_match -Wno_return --no_native _build/test/lib/ex_twiml/ebin
after_script:
- MIX_ENV=docs mix deps.get
- MIX_ENV=docs mix inch.report
- MIX_ENV=docs mix deps.get
- MIX_ENV=docs mix inch.report
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
34 changes: 34 additions & 0 deletions lib/ex_twiml/reserved_name_error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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
@spec exception(list) :: %__MODULE__{}
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