diff --git a/rebar.config b/rebar.config index a6475089..1c71a9c0 100644 --- a/rebar.config +++ b/rebar.config @@ -1,2 +1,17 @@ {edoc_opts, [{preprocess, true}]}. {erl_opts, [debug_info]}. +{dialyzer, [ + {warnings, [ + unknown, + unmatched_returns, + error_handling, + underspecs + ]} +]}. +{profiles, [ + {test, [ + {dialyzer, [ + {plt_extra_apps, [eunit]} + ]} + ]} +]}. diff --git a/src/jsx.erl b/src/jsx.erl index 1b4ceed4..d02d33bd 100644 --- a/src/jsx.erl +++ b/src/jsx.erl @@ -54,20 +54,20 @@ -type config() :: jsx_config:config(). --spec encode(Source::json_term()) -> json_text(). +-spec encode(Source::json_term()) -> json_text() | {incomplete, encoder()}. encode(Source) -> encode(Source, []). --spec encode(Source::json_term(), Config::jsx_to_json:config()) -> json_text() | {incomplete, encoder()}. +-spec encode(Source::json_term(), Config::jsx_config:options()) -> json_text() | {incomplete, encoder()}. encode(Source, Config) -> jsx_to_json:to_json(Source, Config). --spec decode(Source::json_text()) -> json_term(). +-spec decode(Source::json_text()) -> json_term() | {incomplete, decoder()}. decode(Source) -> decode(Source, []). --spec decode(Source::json_text(), Config::jsx_to_term:config()) -> json_term() | {incomplete, decoder()}. +-spec decode(Source::json_text(), Config::jsx_config:options()) -> json_term() | {incomplete, decoder()}. decode(Source, Config) -> jsx_to_term:to_term(Source, Config). @@ -76,7 +76,7 @@ decode(Source, Config) -> jsx_to_term:to_term(Source, Config). format(Source) -> format(Source, []). --spec format(Source::json_text(), Config::jsx_to_json:config()) -> json_text() | {incomplete, decoder()}. +-spec format(Source::json_text(), Config::jsx_config:options()) -> json_text(). format(Source, Config) -> jsx_to_json:format(Source, Config). @@ -91,43 +91,44 @@ minify(Source) -> format(Source, []). prettify(Source) -> format(Source, [space, {indent, 2}]). --spec is_json(Source::any()) -> boolean(). +-spec is_json(Source::binary()) -> boolean() | {incomplete, decoder()}. is_json(Source) -> is_json(Source, []). --spec is_json(Source::any(), Config::jsx_verify:config()) -> boolean() | {incomplete, decoder()}. +-spec is_json(Source::binary(), Config::jsx_config:options()) -> boolean() | {incomplete, decoder()}. is_json(Source, Config) -> jsx_verify:is_json(Source, Config). --spec is_term(Source::any()) -> boolean(). +-spec is_term(Source::json_term() | end_stream | end_json) -> boolean() | {incomplete, encoder()}. is_term(Source) -> is_term(Source, []). --spec is_term(Source::any(), Config::jsx_verify:config()) -> boolean() | {incomplete, encoder()}. +-spec is_term(Source::json_term() | end_stream | end_json, + Config::jsx_config:options()) -> boolean() | {incomplete, encoder()}. is_term(Source, Config) -> jsx_verify:is_term(Source, Config). --spec consult(File::file:name_all()) -> list(json_term()). +-spec consult(File::file:name_all()) -> list(jsx_consult:json_value()). consult(File) -> consult(File, []). --spec consult(File::file:name_all(), Config::jsx_to_term:config()) -> list(json_term()). +-spec consult(File::file:name_all(), Config::jsx_consult:config()) -> list(jsx_consult:json_value()). consult(File, Config) -> jsx_consult:consult(File, Config). -type decoder() :: fun((json_text() | end_stream | end_json) -> any()). --spec decoder(Handler::module(), State::any(), Config::list()) -> decoder(). +-spec decoder(Handler::module(), State::any(), Config::jsx_config:options()) -> decoder(). decoder(Handler, State, Config) -> jsx_decoder:decoder(Handler, State, Config). -type encoder() :: fun((json_term() | end_stream | end_json) -> any()). --spec encoder(Handler::module(), State::any(), Config::list()) -> encoder(). +-spec encoder(Handler::module(), State::any(), Config::jsx_config:options()) -> encoder(). encoder(Handler, State, Config) -> jsx_encoder:encoder(Handler, State, Config). @@ -156,13 +157,14 @@ encoder(Handler, State, Config) -> jsx_encoder:encoder(Handler, State, Config). -type parser() :: fun((token() | end_stream) -> any()). --spec parser(Handler::module(), State::any(), Config::list()) -> parser(). +-spec parser(Handler::module(), State::any(), Config::jsx_config:options()) -> parser(). parser(Handler, State, Config) -> jsx_parser:parser(Handler, State, Config). -opaque internal_state() :: tuple(). --spec resume(Term::json_text() | token(), InternalState::internal_state(), Config::list()) -> any(). +-spec resume(Term::json_text() | token(), InternalState::internal_state(), + Config::jsx_config:options()) -> jsx:decoder() | {incomplete, jsx:decoder()}. resume(Term, {decoder, State, Handler, Acc, Stack}, Config) -> jsx_decoder:resume(Term, State, Handler, Acc, Stack, jsx_config:parse_config(Config)); diff --git a/src/jsx_config.erl b/src/jsx_config.erl index 47cbcf72..ba1d872a 100644 --- a/src/jsx_config.erl +++ b/src/jsx_config.erl @@ -49,8 +49,55 @@ -type config() :: #config{}. -export_type([config/0]). +-type option() :: valid_flag() + | {valid_flag(), boolean()} + | {strict, [strict_option()]} + | {error_handler, fun((any(), any(), any()) -> ok)} + | {incomplete_handler, fun((any(), any(), any()) -> ok)} + | {return_maps, boolean()} + | {labels, label_option()} + | {space, non_neg_integer()} + | {indent, non_neg_integer()} + | {depth, non_neg_integer()} + | {newline, binary()} + | legacy_option() + | {legacy_option(), boolean()}. +-type legacy_option() :: strict_comments + | strict_commas + | strict_utf8 + | strict_single_quotes + | strict_escapes + | strict_control_codes. + +-type options() :: [option()]. +-export_type([options/0]). + +-type strict_option() :: comments + | trailing_commas + | utf8 + | single_quotes + | escapes + | control_codes. +-type label_option() :: binary + | atom + | existing_atom + | attempt_atom. + +-type valid_flag() :: escaped_forward_slashes + | escaped_strings + | unescaped_jsonp + | dirty_strings + | multi_term + | return_tail + | repeat_keys + | strict + | stream + | uescape + | error_handler + | incomplete_handler. + %% parsing of jsx config --spec parse_config(Config::proplists:proplist()) -> config(). +-spec parse_config(Config::options()) -> config(). parse_config(Config) -> parse_config(Config, #config{}). @@ -116,7 +163,7 @@ parse_strict(_Strict, _Rest, _Config) -> --spec config_to_list(Config::config()) -> proplists:proplist(). +-spec config_to_list(Config::config()) -> options(). config_to_list(Config) -> reduce_config(lists:map( @@ -153,7 +200,7 @@ reduce_config([Else|Input], Output, Strict) -> reduce_config(Input, [Else] ++ Output, Strict). --spec valid_flags() -> [atom()]. +-spec valid_flags() -> [valid_flag(), ...]. valid_flags() -> [ @@ -172,7 +219,7 @@ valid_flags() -> ]. --spec extract_config(Config::proplists:proplist()) -> proplists:proplist(). +-spec extract_config(Config::options()) -> options(). extract_config(Config) -> extract_parser_config(Config, []). diff --git a/src/jsx_consult.erl b/src/jsx_consult.erl index d25306ab..e0e73c97 100644 --- a/src/jsx_consult.erl +++ b/src/jsx_consult.erl @@ -32,7 +32,7 @@ return_maps = false }). --type config() :: list(). +-type config() :: proplists:proplist(). -export_type([config/0]). -type json_value() :: list(json_value()) @@ -43,6 +43,7 @@ | integer() | float() | binary(). +-export_type([json_value/0]). opts(Opts) -> [return_maps, multi_term] ++ Opts. @@ -61,8 +62,8 @@ consult(File, Config) when is_list(Config) -> end. --type state() :: {[], proplists:proplist(), {list(), #config{}}}. --spec init(Config::proplists:proplist()) -> state(). +-type state() :: {[], config(), {list(), #config{}}}. +-spec init(Config::config()) -> state(). init(Config) -> {[], Config, jsx_to_term:start_term(Config)}. diff --git a/src/jsx_decoder.erl b/src/jsx_decoder.erl index 7d92c0d1..a706c891 100644 --- a/src/jsx_decoder.erl +++ b/src/jsx_decoder.erl @@ -32,7 +32,7 @@ -export([decoder/3, resume/6]). --spec decoder(Handler::module(), State::any(), Config::list()) -> jsx:decoder(). +-spec decoder(Handler::module(), State::any(), Config::jsx_config:options()) -> jsx:decoder(). decoder(Handler, State, Config) -> fun(JSON) -> start(JSON, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end. diff --git a/src/jsx_encoder.erl b/src/jsx_encoder.erl index c6db8efb..a1242a79 100644 --- a/src/jsx_encoder.erl +++ b/src/jsx_encoder.erl @@ -25,19 +25,19 @@ -export([encoder/3, encode/1, encode/2]). --spec encoder(Handler::module(), State::any(), Config::list()) -> jsx:encoder(). +-spec encoder(Handler::module(), State::any(), Config::jsx_config:options()) -> jsx:encoder(). encoder(Handler, State, Config) -> Parser = jsx:parser(Handler, State, Config), fun(Term) -> Parser(encode(Term) ++ [end_json]) end. --spec encode(Term::any()) -> any(). +-spec encode(Term::any()) -> [any(), ...]. encode(Term) -> encode(Term, ?MODULE). --spec encode(Term::any(), EntryPoint::module()) -> any(). +-spec encode(Term::any(), EntryPoint::module()) -> [any(), ...]. encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 -> [start_object, end_object]; diff --git a/src/jsx_parser.erl b/src/jsx_parser.erl index 070c9344..8506b037 100644 --- a/src/jsx_parser.erl +++ b/src/jsx_parser.erl @@ -27,7 +27,7 @@ -export([init/1, handle_event/2]). --spec parser(Handler::module(), State::any(), Config::list()) -> jsx:parser(). +-spec parser(Handler::module(), State::any(), Config::jsx_config:options()) -> jsx:parser(). parser(Handler, State, Config) -> fun(Tokens) -> value(Tokens, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end. @@ -630,7 +630,7 @@ to_hex(X) -> X + 48. %% ascii "1" is [49], "2" is [50], etc... %% for raw input --spec init(proplists:proplist()) -> list(). +-spec init([]) -> []. init([]) -> []. diff --git a/src/jsx_to_json.erl b/src/jsx_to_json.erl index fb14df3d..d20add61 100644 --- a/src/jsx_to_json.erl +++ b/src/jsx_to_json.erl @@ -36,17 +36,16 @@ newline = <<$\n>> }). --type config() :: list(). --export_type([config/0]). +-type config() :: proplists:proplist(). --spec to_json(Source::any(), Config::config()) -> binary(). +-spec to_json(Source::jsx:json_term(), Config::jsx_config:options()) -> binary(). to_json(Source, Config) when is_list(Config) -> (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config ++ [escaped_strings])))(Source). --spec format(Source::binary(), Config::config()) -> binary(). +-spec format(Source::binary(), Config::jsx_config:options()) -> jsx:json_text(). format(Source, Config) when is_binary(Source) andalso is_list(Config) -> (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config ++ [escaped_strings])))(Source); @@ -91,7 +90,7 @@ parse_config([], Config) -> -type state() :: {unicode:charlist(), #config{}}. --spec init(Config::proplists:proplist()) -> state(). +-spec init(Config::config()) -> state(). init(Config) -> {[], parse_config(Config)}. @@ -390,7 +389,7 @@ custom_newline_test_() -> [ {"single key object", ?_assert( jsx:format(<<"{\"k\":\"v\"}">>, [space, {indent, 2}, {newline, <<$\r>>}]) - =:= <<"{\r \"k\": \"v\"\r}">>) + =:= <<"{\r \"k\": \"v\"\r}">>) } ]. diff --git a/src/jsx_to_term.erl b/src/jsx_to_term.erl index e3c99e7c..07beba4d 100644 --- a/src/jsx_to_term.erl +++ b/src/jsx_to_term.erl @@ -41,21 +41,9 @@ return_maps = false }). --type config() :: list(). --export_type([config/0]). - --type json_value() :: list(json_value()) - | list({binary() | atom(), json_value()}) | [{},...] - | {with_tail, json_value(), binary()} - | map() - | true - | false - | null - | integer() - | float() - | binary(). - --spec to_term(Source::binary(), Config::config()) -> json_value(). +-type config() :: proplists:proplist(). + +-spec to_term(Source::binary(), Config::jsx_config:options()) -> jsx:json_term() | {incomplete, jsx:decoder()}. to_term(Source, Config) when is_list(Config) -> (jsx:decoder(?MODULE, [return_maps] ++ Config, jsx_config:extract_config(Config)))(Source). @@ -87,7 +75,7 @@ parse_config([], Config) -> -type state() :: {list(), #config{}}. --spec init(Config::proplists:proplist()) -> state(). +-spec init(Config::config()) -> state(). init(Config) -> start_term(Config). diff --git a/src/jsx_verify.erl b/src/jsx_verify.erl index 5f4a3d89..5eef4d29 100644 --- a/src/jsx_verify.erl +++ b/src/jsx_verify.erl @@ -26,8 +26,9 @@ -export([is_json/2, is_term/2]). -export([init/1, handle_event/2]). +-type config() :: proplists:proplist(). --spec is_json(Source::binary(), Config::proplists:proplist()) -> true | false | {incomplete, jsx:decoder()}. +-spec is_json(Source::binary(), Config::jsx_config:options()) -> true | false | {incomplete, jsx:decoder()}. is_json(Source, Config) when is_list(Config) -> try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source) @@ -35,7 +36,8 @@ is_json(Source, Config) when is_list(Config) -> end. --spec is_term(Source::any(), Config::proplists:proplist()) -> true | false | {incomplete, jsx:encoder()}. +-spec is_term(Source::jsx:json_term() | end_stream | end_json, + Config::jsx_config:options()) -> true | false | {incomplete, jsx:encoder()}. is_term(Source, Config) when is_list(Config) -> try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source) @@ -68,7 +70,7 @@ parse_config([], Config) -> %% we don't actually need any state for this -type state() :: []. --spec init(Config::proplists:proplist()) -> state(). +-spec init(Config::config()) -> state(). init(Config) -> parse_config(Config).