diff --git a/src/elvis_style.erl b/src/elvis_style.erl index 4b037cc8..14311707 100644 --- a/src/elvis_style.erl +++ b/src/elvis_style.erl @@ -12,8 +12,7 @@ atom_naming_convention/3, no_throw/3, no_dollar_space/3, no_author/3, no_import/3, no_catch_expressions/3, no_single_clause_case/3, numeric_format/3, behaviour_spelling/3, always_shortcircuit/3, consistent_generic_type/3, export_used_types/3, - no_match_in_condition/3, param_pattern_matching/3, private_data_types/3, option/3, - prefer_unquoted_atoms/3]). + no_match_in_condition/3, param_pattern_matching/3, private_data_types/3, option/3]). -export_type([empty_rule_config/0]). -export_type([ignorable/0]). @@ -108,9 +107,6 @@ -define(ATOM_NAMING_CONVENTION_MSG, "Atom ~p on line ~p does not respect the format " "defined by the regular expression '~p'."). --define(ATOM_PREFERRED_QUOTES_MSG, - "Atom ~p on line ~p is quoted " - "but quotes are not needed."). -define(NO_THROW_MSG, "Usage of throw/1 on line ~p is not recommended"). -define(NO_DOLLAR_SPACE_MSG, "'$ ' was found on line ~p. It's use is discouraged. " @@ -1019,16 +1015,6 @@ atom_naming_convention(Config, Target, RuleConfig) -> AtomNodes = elvis_code:find(fun is_atom_node/1, Root, #{traverse => all, mode => node}), check_atom_names(Regex, RegexEnclosed, AtomNodes, []). --spec prefer_unquoted_atoms(elvis_config:config(), - elvis_file:file(), - empty_rule_config()) -> - [elvis_result:item()]. -prefer_unquoted_atoms(_Config, Target, _RuleConfig) -> - {Content, #{encoding := _Encoding}} = elvis_file:src(Target), - Tree = ktn_code:parse_tree(Content), - AtomNodes = elvis_code:find(fun is_atom_node/1, Tree, #{traverse => all, mode => node}), - check_atom_quotes(AtomNodes, []). - -spec no_throw(elvis_config:config(), elvis_file:file(), empty_rule_config()) -> [elvis_result:item()]. no_throw(Config, Target, RuleConfig) -> @@ -1470,36 +1456,6 @@ check_atom_names(Regex, RegexEnclosed, [AtomNode | RemainingAtomNodes], AccIn) - end, check_atom_names(Regex, RegexEnclosed, RemainingAtomNodes, AccOut). -%% @private -check_atom_quotes([] = _AtomNodes, Acc) -> - Acc; -check_atom_quotes([AtomNode | RemainingAtomNodes], AccIn) -> - AtomName = ktn_code:attr(text, AtomNode), - ValueAtomName = ktn_code:attr(value, AtomNode), - - IsException = is_exception_prefer_quoted(ValueAtomName), - - AccOut = - case unicode:characters_to_list(AtomName, unicode) of - [$' | _] when not IsException -> - Msg = ?ATOM_PREFERRED_QUOTES_MSG, - {Line, _} = ktn_code:attr(location, AtomNode), - Info = [AtomName, Line], - Result = elvis_result:new(item, Msg, Info, Line), - AccIn ++ [Result]; - _ -> - AccIn - end, - check_atom_quotes(RemainingAtomNodes, AccOut). - -%% @private -is_exception_prefer_quoted(Elem) -> - KeyWords = - ['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl', 'bsr', 'bxor', 'case', - 'catch', 'cond', 'div', 'end', 'fun', 'if', 'let', 'not', 'of', 'or', 'orelse', 'receive', - 'rem', 'try', 'when', 'xor'], - lists:member(Elem, KeyWords). - %% @private string_strip_enclosed([$' | Rest]) -> [$' | Reversed] = lists:reverse(Rest), diff --git a/src/elvis_text_style.erl b/src/elvis_text_style.erl index c560d166..d677a9e7 100644 --- a/src/elvis_text_style.erl +++ b/src/elvis_text_style.erl @@ -1,19 +1,26 @@ -module(elvis_text_style). --export([default/1, line_length/3, no_tabs/3, no_trailing_whitespace/3]). +-export([default/1, line_length/3, no_tabs/3, no_trailing_whitespace/3, + prefer_unquoted_atoms/3]). -export_type([line_length_config/0, no_trailing_whitespace_config/0]). -define(LINE_LENGTH_MSG, "Line ~p is too long. It has ~p characters."). -define(NO_TABS_MSG, "Line ~p has a tab at column ~p."). -define(NO_TRAILING_WHITESPACE_MSG, "Line ~b has ~b trailing whitespace characters."). +-define(ATOM_PREFERRED_QUOTES_MSG, + "Atom ~p on line ~p is quoted " + "but quotes are not needed."). % These are part of a non-declared "behaviour" % The reason why we don't try to handle them with different arity is % that arguments are ignored in different positions (1 and 3) so that'd % probably be messier than to ignore the warning -hank([{unnecessary_function_arguments, - [{no_trailing_whitespace, 3}, {no_tabs, 3}, {line_length, 3}]}]). + [{no_trailing_whitespace, 3}, + {no_tabs, 3}, + {line_length, 3}, + {prefer_unquoted_atoms, 3}]}]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Default values @@ -71,6 +78,38 @@ no_trailing_whitespace(_Config, Target, RuleConfig) -> end, RuleConfig). +-spec prefer_unquoted_atoms(elvis_config:config(), + elvis_file:file(), + elvis_style:empty_rule_config()) -> + [elvis_result:item()]. +prefer_unquoted_atoms(_Config, Target, _RuleConfig) -> + {Content, #{encoding := _Encoding}} = elvis_file:src(Target), + Tree = ktn_code:parse_tree(Content), + AtomNodes = elvis_code:find(fun is_atom_node/1, Tree, #{traverse => all, mode => node}), + check_atom_quotes(AtomNodes, []). + +%% @private +check_atom_quotes([] = _AtomNodes, Acc) -> + Acc; +check_atom_quotes([AtomNode | RemainingAtomNodes], AccIn) -> + AtomName = ktn_code:attr(text, AtomNode), + ValueAtomName = ktn_code:attr(value, AtomNode), + + IsException = is_exception_prefer_quoted(ValueAtomName), + + AccOut = + case unicode:characters_to_list(AtomName, unicode) of + [$' | _] when not IsException -> + Msg = ?ATOM_PREFERRED_QUOTES_MSG, + {Line, _} = ktn_code:attr(location, AtomNode), + Info = [AtomName, Line], + Result = elvis_result:new(item, Msg, Info, Line), + AccIn ++ [Result]; + _ -> + AccIn + end, + check_atom_quotes(RemainingAtomNodes, AccOut). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Private %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -164,6 +203,18 @@ check_no_trailing_whitespace(Line, Num, IgnoreEmptyLines) -> {ok, Result} end. +%% @private +is_atom_node(MaybeAtom) -> + ktn_code:type(MaybeAtom) =:= atom. + +%% @private +is_exception_prefer_quoted(Elem) -> + KeyWords = + ['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl', 'bsr', 'bxor', 'case', + 'catch', 'cond', 'div', 'end', 'fun', 'if', 'let', 'not', 'of', 'or', 'orelse', 'receive', + 'rem', 'try', 'when', 'xor'], + lists:member(Elem, KeyWords). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Internal Function Definitions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/test/style_SUITE.erl b/test/style_SUITE.erl index d53b0761..8ebfaea0 100644 --- a/test/style_SUITE.erl +++ b/test/style_SUITE.erl @@ -1300,10 +1300,12 @@ verify_no_successive_maps(_Config) -> -spec verify_unquoted_atoms(config()) -> any(). verify_unquoted_atoms(Config) -> PassPath = "pass_unquoted_atoms." ++ "erl", - [] = elvis_core_apply_rule(Config, elvis_style, prefer_unquoted_atoms, #{}, PassPath), + [] = + elvis_core_apply_rule(Config, elvis_text_style, prefer_unquoted_atoms, #{}, PassPath), FailPath = "fail_quoted_atoms." ++ "erl", - [_, _] = elvis_core_apply_rule(Config, elvis_style, prefer_unquoted_atoms, #{}, FailPath). + [_, _] = + elvis_core_apply_rule(Config, elvis_text_style, prefer_unquoted_atoms, #{}, FailPath). -spec verify_atom_naming_convention(config()) -> any(). verify_atom_naming_convention(Config) ->