Skip to content

Commit ba01eca

Browse files
committed
Getting ready for 0.4.0 release
1 parent 58f8b52 commit ba01eca

File tree

8 files changed

+574
-28
lines changed

8 files changed

+574
-28
lines changed

.credo.exs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# This file contains the configuration for Credo and you are probably reading
2+
# this after creating it with `mix credo.gen.config`.
3+
#
4+
# If you find anything wrong or unclear in this file, please report an
5+
# issue on GitHub: https://github.com/rrrene/credo/issues
6+
#
7+
%{
8+
#
9+
# You can have as many configs as you like in the `configs:` field.
10+
configs: [
11+
%{
12+
#
13+
# Run any config using `mix credo -C <name>`. If no config name is given
14+
# "default" is used.
15+
#
16+
name: "default",
17+
#
18+
# These are the files included in the analysis:
19+
files: %{
20+
#
21+
# You can give explicit globs or simply directories.
22+
# In the latter case `**/*.{ex,exs}` will be used.
23+
#
24+
included: [
25+
"lib/",
26+
"src/",
27+
"test/",
28+
"web/",
29+
"apps/*/lib/",
30+
"apps/*/src/",
31+
"apps/*/test/",
32+
"apps/*/web/"
33+
],
34+
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
35+
},
36+
#
37+
# Load and configure plugins here:
38+
#
39+
plugins: [],
40+
#
41+
# If you create your own checks, you must specify the source files for
42+
# them here, so they can be loaded by Credo before running the analysis.
43+
#
44+
requires: [],
45+
#
46+
# If you want to enforce a style guide and need a more traditional linting
47+
# experience, you can change `strict` to `true` below:
48+
#
49+
strict: false,
50+
#
51+
# To modify the timeout for parsing files, change this value:
52+
#
53+
parse_timeout: 5000,
54+
#
55+
# If you want to use uncolored output by default, you can change `color`
56+
# to `false` below:
57+
#
58+
color: true,
59+
#
60+
# You can customize the parameters of any check by adding a second element
61+
# to the tuple.
62+
#
63+
# To disable a check put `false` as second element:
64+
#
65+
# {Credo.Check.Design.DuplicatedCode, false}
66+
#
67+
checks: %{
68+
enabled: [
69+
#
70+
## Consistency Checks
71+
#
72+
{Credo.Check.Consistency.ExceptionNames, []},
73+
{Credo.Check.Consistency.LineEndings, []},
74+
{Credo.Check.Consistency.ParameterPatternMatching, []},
75+
{Credo.Check.Consistency.SpaceAroundOperators, []},
76+
{Credo.Check.Consistency.SpaceInParentheses, []},
77+
{Credo.Check.Consistency.TabsOrSpaces, []},
78+
79+
#
80+
## Design Checks
81+
#
82+
# You can customize the priority of any check
83+
# Priority values are: `low, normal, high, higher`
84+
#
85+
{Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
86+
# You can also customize the exit_status of each check.
87+
# If you don't want TODO comments to cause `mix credo` to fail, just
88+
# set this value to 0 (zero).
89+
#
90+
{Credo.Check.Design.TagTODO, [exit_status: 2]},
91+
{Credo.Check.Design.TagFIXME, []},
92+
93+
#
94+
## Readability Checks
95+
#
96+
{Credo.Check.Readability.AliasOrder, []},
97+
{Credo.Check.Readability.FunctionNames, []},
98+
{Credo.Check.Readability.LargeNumbers, []},
99+
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
100+
{Credo.Check.Readability.ModuleAttributeNames, []},
101+
{Credo.Check.Readability.ModuleDoc, []},
102+
{Credo.Check.Readability.ModuleNames, []},
103+
{Credo.Check.Readability.ParenthesesInCondition, []},
104+
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
105+
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
106+
{Credo.Check.Readability.PredicateFunctionNames, []},
107+
{Credo.Check.Readability.PreferImplicitTry, []},
108+
{Credo.Check.Readability.RedundantBlankLines, []},
109+
{Credo.Check.Readability.Semicolons, []},
110+
{Credo.Check.Readability.SpaceAfterCommas, []},
111+
{Credo.Check.Readability.StringSigils, []},
112+
{Credo.Check.Readability.TrailingBlankLine, []},
113+
{Credo.Check.Readability.TrailingWhiteSpace, []},
114+
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
115+
{Credo.Check.Readability.VariableNames, []},
116+
{Credo.Check.Readability.WithSingleClause, []},
117+
118+
#
119+
## Refactoring Opportunities
120+
#
121+
{Credo.Check.Refactor.Apply, []},
122+
{Credo.Check.Refactor.CondStatements, []},
123+
{Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 12]},
124+
{Credo.Check.Refactor.FunctionArity, []},
125+
{Credo.Check.Refactor.LongQuoteBlocks, []},
126+
{Credo.Check.Refactor.MatchInCondition, []},
127+
{Credo.Check.Refactor.MapJoin, []},
128+
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
129+
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
130+
{Credo.Check.Refactor.Nesting, []},
131+
{Credo.Check.Refactor.UnlessWithElse, []},
132+
{Credo.Check.Refactor.WithClauses, []},
133+
{Credo.Check.Refactor.FilterFilter, []},
134+
{Credo.Check.Refactor.RejectReject, []},
135+
{Credo.Check.Refactor.RedundantWithClauseResult, []},
136+
137+
#
138+
## Warnings
139+
#
140+
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
141+
{Credo.Check.Warning.BoolOperationOnSameValues, []},
142+
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
143+
{Credo.Check.Warning.IExPry, []},
144+
{Credo.Check.Warning.IoInspect, []},
145+
{Credo.Check.Warning.OperationOnSameValues, []},
146+
{Credo.Check.Warning.OperationWithConstantResult, []},
147+
{Credo.Check.Warning.RaiseInsideRescue, []},
148+
{Credo.Check.Warning.SpecWithStruct, []},
149+
{Credo.Check.Warning.WrongTestFileExtension, []},
150+
{Credo.Check.Warning.UnusedEnumOperation, []},
151+
{Credo.Check.Warning.UnusedFileOperation, []},
152+
{Credo.Check.Warning.UnusedKeywordOperation, []},
153+
{Credo.Check.Warning.UnusedListOperation, []},
154+
{Credo.Check.Warning.UnusedPathOperation, []},
155+
{Credo.Check.Warning.UnusedRegexOperation, []},
156+
{Credo.Check.Warning.UnusedStringOperation, []},
157+
{Credo.Check.Warning.UnusedTupleOperation, []},
158+
{Credo.Check.Warning.UnsafeExec, []}
159+
],
160+
disabled: [
161+
#
162+
# Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`)
163+
164+
#
165+
# Controversial and experimental checks (opt-in, just move the check to `:enabled`
166+
# and be sure to use `mix credo --strict` to see low priority checks)
167+
#
168+
{Credo.Check.Consistency.MultiAliasImportRequireUse, []},
169+
{Credo.Check.Consistency.UnusedVariableNames, []},
170+
{Credo.Check.Design.DuplicatedCode, []},
171+
{Credo.Check.Design.SkipTestWithoutComment, []},
172+
{Credo.Check.Readability.AliasAs, []},
173+
{Credo.Check.Readability.BlockPipe, []},
174+
{Credo.Check.Readability.ImplTrue, []},
175+
{Credo.Check.Readability.MultiAlias, []},
176+
{Credo.Check.Readability.NestedFunctionCalls, []},
177+
{Credo.Check.Readability.SeparateAliasRequire, []},
178+
{Credo.Check.Readability.SingleFunctionToBlockPipe, []},
179+
{Credo.Check.Readability.SinglePipe, []},
180+
{Credo.Check.Readability.Specs, []},
181+
{Credo.Check.Readability.StrictModuleLayout, []},
182+
{Credo.Check.Readability.WithCustomTaggedTuple, []},
183+
{Credo.Check.Refactor.ABCSize, []},
184+
{Credo.Check.Refactor.AppendSingleItem, []},
185+
{Credo.Check.Refactor.DoubleBooleanNegation, []},
186+
{Credo.Check.Refactor.FilterReject, []},
187+
{Credo.Check.Refactor.IoPuts, []},
188+
{Credo.Check.Refactor.MapMap, []},
189+
{Credo.Check.Refactor.ModuleDependencies, []},
190+
{Credo.Check.Refactor.NegatedIsNil, []},
191+
{Credo.Check.Refactor.PipeChainStart, []},
192+
{Credo.Check.Refactor.RejectFilter, []},
193+
{Credo.Check.Refactor.VariableRebinding, []},
194+
{Credo.Check.Warning.LazyLogging, []},
195+
{Credo.Check.Warning.LeakyEnvironment, []},
196+
{Credo.Check.Warning.MapGetUnsafePass, []},
197+
{Credo.Check.Warning.MixEnv, []},
198+
{Credo.Check.Warning.UnsafeToAtom, []}
199+
200+
# {Credo.Check.Refactor.MapInto, []},
201+
202+
#
203+
# Custom checks can be created using `mix credo.gen.check`.
204+
#
205+
]
206+
}
207+
}
208+
]
209+
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.4.0] - 2021-04-27
11+
12+
### Fixed
13+
14+
- Calls to `render_component` now evaluate the AST aliases in the context of the `__CALLER__`
15+
- EEx templates, components and layouts are tokenized prior to going through the MJML EEx engine as not to escape MJML content
16+
1017
## [0.3.0] - 2021-04-17
1118

1219
### Added

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ dependencies in `mix.exs`:
4242
```elixir
4343
def deps do
4444
[
45-
{:mjml_eex, "~> 0.3.0"}
45+
{:mjml_eex, "~> 0.4.0"}
4646
]
4747
end
4848
```
@@ -78,7 +78,7 @@ Checkout my [GitHub Sponsorship page](https://github.com/sponsors/akoutmos) if y
7878

7979
### Basic Usage
8080

81-
Add `{:mjml_eex, "~> 0.3.0"}` to your `mix.exs` file and run `mix deps.get`. After you have that in place, you
81+
Add `{:mjml_eex, "~> 0.4.0"}` to your `mix.exs` file and run `mix deps.get`. After you have that in place, you
8282
can go ahead and create a template module like so:
8383

8484
```elixir

lib/engines/mjml.ex

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ defmodule MjmlEEx.Engines.Mjml do
88
@behaviour EEx.Engine
99

1010
@impl true
11-
defdelegate init(opts), to: EEx.Engine
11+
def init(opts) do
12+
{caller, remaining_opts} = Keyword.pop!(opts, :caller)
13+
14+
remaining_opts
15+
|> EEx.Engine.init()
16+
|> Map.put(:caller, caller)
17+
end
1218

1319
@impl true
1420
defdelegate handle_body(state), to: EEx.Engine
@@ -23,31 +29,32 @@ defmodule MjmlEEx.Engines.Mjml do
2329
defdelegate handle_text(state, meta, text), to: EEx.Engine
2430

2531
@impl true
26-
def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, module}]}) do
27-
do_render_component(state, module, [])
32+
def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, _module} = aliases]}) do
33+
module = Macro.expand(aliases, state.caller)
34+
35+
do_render_component(state, module, [], state.caller)
2836
end
2937

30-
def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, module}, opts]}) do
31-
do_render_component(state, module, opts)
38+
def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, _module} = aliases, opts]}) do
39+
module = Macro.expand(aliases, state.caller)
40+
41+
do_render_component(state, module, opts, state.caller)
3242
end
3343

3444
def handle_expr(_state, _marker, {:render_component, _, _}) do
3545
raise "render_component can only be invoked inside of an <%= ... %> expression"
3646
end
3747

38-
def handle_expr(state, marker, expr) do
39-
encoded_expression = Utils.encode_expression(marker, expr)
40-
41-
%{binary: binary} = state
42-
%{state | binary: [encoded_expression | binary]}
48+
def handle_expr(_state, marker, expr) do
49+
raise "Unescaped expression. This should never happen and is most likely a bug in MJML EEx: <%#{marker} #{Macro.to_string(expr)} %>"
4350
end
4451

45-
defp do_render_component(state, module_alias_list, opts) do
52+
defp do_render_component(state, module, opts, caller) do
4653
{mjml_component, _} =
47-
module_alias_list
48-
|> Module.concat()
54+
module
4955
|> apply(:render, [opts])
50-
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true)
56+
|> Utils.escape_eex_expressions()
57+
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true, caller: caller)
5158
|> Code.eval_quoted()
5259

5360
%{binary: binary} = state

lib/mjml_eex.ex

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ defmodule MjmlEEx do
5656

5757
layout_module = Keyword.get(opts, :layout, false)
5858

59-
phoenix_html_ast =
59+
{phoenix_html_ast, escaped_mjml_document} =
6060
if layout_module do
6161
layout_module = Macro.expand(layout_module, __CALLER__)
6262

6363
Code.ensure_compiled!(layout_module)
64-
compile_with_layout(mjml_template, layout_module)
64+
compile_with_layout(mjml_template, layout_module, __CALLER__)
6565
else
66-
compile_file(mjml_template)
66+
compile_file(mjml_template, __CALLER__)
6767
end
6868

6969
created_code =
@@ -75,6 +75,11 @@ defmodule MjmlEEx do
7575
@external_resource unquote(layout_module).__layout_file__()
7676
end
7777

78+
@doc "Returns the escaped MJML template. Useful for debugging rendering issues."
79+
def debug_mjml_template do
80+
unquote(escaped_mjml_document)
81+
end
82+
7883
@doc "Safely render the MJML template using Phoenix.HTML"
7984
def render(assigns) do
8085
assigns
@@ -94,27 +99,30 @@ defmodule MjmlEEx do
9499
created_code
95100
end
96101

97-
defp compile_file(template_path) do
102+
defp compile_file(template_path, caller) do
98103
{mjml_document, _} =
99104
template_path
100-
|> EEx.compile_file(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true)
105+
|> File.read!()
106+
|> Utils.escape_eex_expressions()
107+
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true, caller: caller)
101108
|> Code.eval_quoted()
102109

103-
compile_mjml_document(mjml_document)
110+
{compile_mjml_document(mjml_document), mjml_document}
104111
end
105112

106-
defp compile_with_layout(template_path, layout_module) do
113+
defp compile_with_layout(template_path, layout_module, caller) do
107114
template_file_contents = File.read!(template_path)
108115
pre_inner_content = layout_module.pre_inner_content()
109116
post_inner_content = layout_module.post_inner_content()
110117

111118
{mjml_document, _} =
112119
[pre_inner_content, template_file_contents, post_inner_content]
113120
|> Enum.join()
114-
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true)
121+
|> Utils.escape_eex_expressions()
122+
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true, caller: caller)
115123
|> Code.eval_quoted()
116124

117-
compile_mjml_document(mjml_document)
125+
{compile_mjml_document(mjml_document), mjml_document}
118126
end
119127

120128
defp compile_mjml_document(mjml_document) do

0 commit comments

Comments
 (0)