-module(datadog_query@lint). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/datadog_query/lint.gleam"). -export([at_prefixed_attrs/1, unbalanced_braces/1, unbalanced_parens/1, dangling_joiner/1, reserved_word_as_tag_name/1, wildcard_in_quoted_value/1, empty_scope_with_grouping/1]). -export_type([balance_error/0, balance_state/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type balance_error() :: {unmatched, binary()} | {unclosed, binary()}. -type balance_state() :: balance_ok | balance_unmatched | balance_unclosed. -file("src/datadog_query/lint.gleam", 35). -spec token_to_at_attr(binary()) -> {ok, binary()} | {error, nil}. token_to_at_attr(Token) -> Stripped = case gleam_stdlib:string_starts_with(Token, <<"!"/utf8>>) of true -> gleam@string:drop_start(Token, 1); false -> Token end, case gleam_stdlib:string_starts_with(Stripped, <<"@"/utf8>>) of false -> {error, nil}; true -> Attr = case gleam@string:split_once(Stripped, <<":"/utf8>>) of {ok, {Before_colon, _}} -> Before_colon; {error, _} -> Stripped end, case Attr of <<"@"/utf8>> -> {error, nil}; _ -> {ok, Attr} end end. -file("src/datadog_query/lint.gleam", 23). ?DOC( " Scans a Datadog query string for `@`-prefixed attribute names used in\n" " filter position (e.g. `@type:foo` or `@type IN (...)`).\n" "\n" " Datadog metric filters strip the leading `@` from log facets when log-based\n" " metrics are generated — the metric tag for log facet `@type` is just `type`.\n" " A query that filters on `@type` against a log-based metric is rejected by\n" " the SLO API with `400: numerator query is invalid`.\n" "\n" " Returns a deduplicated list of `@`-prefixed attribute names found.\n" ). -spec at_prefixed_attrs(binary()) -> list(binary()). at_prefixed_attrs(Query) -> _pipe = Query, _pipe@1 = gleam@string:replace(_pipe, <<"{"/utf8>>, <<" "/utf8>>), _pipe@2 = gleam@string:replace(_pipe@1, <<"}"/utf8>>, <<" "/utf8>>), _pipe@3 = gleam@string:replace(_pipe@2, <<"("/utf8>>, <<" "/utf8>>), _pipe@4 = gleam@string:replace(_pipe@3, <<")"/utf8>>, <<" "/utf8>>), _pipe@5 = gleam@string:replace(_pipe@4, <<","/utf8>>, <<" "/utf8>>), _pipe@6 = gleam@string:split(_pipe@5, <<" "/utf8>>), _pipe@7 = gleam@list:filter_map(_pipe@6, fun token_to_at_attr/1), gleam@list:unique(_pipe@7). -file("src/datadog_query/lint.gleam", 85). -spec scan_balance(list(binary()), binary(), binary(), integer(), boolean()) -> balance_state(). scan_balance(Graphemes, Open, Close, Depth, In_string) -> case Graphemes of [] -> case Depth of 0 -> balance_ok; _ -> balance_unclosed end; [G | Rest] -> case {In_string, G} of {true, <<"\""/utf8>>} -> scan_balance(Rest, Open, Close, Depth, false); {true, _} -> scan_balance(Rest, Open, Close, Depth, true); {false, <<"\""/utf8>>} -> scan_balance(Rest, Open, Close, Depth, true); {false, _} -> case {G =:= Open, G =:= Close} of {true, _} -> scan_balance(Rest, Open, Close, Depth + 1, false); {_, true} -> case Depth of 0 -> balance_unmatched; _ -> scan_balance( Rest, Open, Close, Depth - 1, false ) end; {_, _} -> scan_balance(Rest, Open, Close, Depth, false) end end end. -file("src/datadog_query/lint.gleam", 66). -spec check_balance(binary(), binary(), binary()) -> {ok, nil} | {error, balance_error()}. check_balance(Query, Open, Close) -> Result = scan_balance( gleam@string:to_graphemes(Query), Open, Close, 0, false ), case Result of balance_ok -> {ok, nil}; balance_unmatched -> {error, {unmatched, Close}}; balance_unclosed -> {error, {unclosed, Open}} end. -file("src/datadog_query/lint.gleam", 57). ?DOC( " Verifies `{` / `}` are balanced. Ignores braces inside double-quoted\n" " strings so `tag:\"a{b}c\"` doesn't trigger a false positive.\n" ). -spec unbalanced_braces(binary()) -> {ok, nil} | {error, balance_error()}. unbalanced_braces(Query) -> check_balance(Query, <<"{"/utf8>>, <<"}"/utf8>>). -file("src/datadog_query/lint.gleam", 62). ?DOC(" Verifies `(` / `)` are balanced (string-aware).\n"). -spec unbalanced_parens(binary()) -> {ok, nil} | {error, balance_error()}. unbalanced_parens(Query) -> check_balance(Query, <<"("/utf8>>, <<")"/utf8>>). -file("src/datadog_query/lint.gleam", 149). -spec check_scope_joiners(binary()) -> list(binary()). check_scope_joiners(Scope) -> Trimmed = gleam@string:trim(Scope), case Trimmed of <<""/utf8>> -> []; _ -> Leading = case gleam_stdlib:string_starts_with( Trimmed, <<"AND "/utf8>> ) orelse gleam_stdlib:string_starts_with(Trimmed, <<"OR "/utf8>>) of true -> [<<"leading joiner: "/utf8, Trimmed/binary>>]; false -> [] end, Trailing = case gleam_stdlib:string_ends_with( Trimmed, <<" AND"/utf8>> ) orelse gleam_stdlib:string_ends_with(Trimmed, <<" OR"/utf8>>) of true -> [<<"trailing joiner: "/utf8, Trimmed/binary>>]; false -> [] end, Doubled = case ((gleam_stdlib:contains_string( Trimmed, <<" AND AND "/utf8>> ) orelse gleam_stdlib:contains_string(Trimmed, <<" OR OR "/utf8>>)) orelse gleam_stdlib:contains_string(Trimmed, <<" AND OR "/utf8>>)) orelse gleam_stdlib:contains_string(Trimmed, <<" OR AND "/utf8>>) of true -> [<<"doubled joiner: "/utf8, Trimmed/binary>>]; false -> [] end, lists:append([Leading, Trailing, Doubled]) end. -file("src/datadog_query/lint.gleam", 133). -spec extract_scopes(binary()) -> list(binary()). extract_scopes(Query) -> case gleam@string:split(Query, <<"{"/utf8>>) of [_ | Rest] -> gleam@list:filter_map( Rest, fun(Chunk) -> case gleam@string:split_once(Chunk, <<"}"/utf8>>) of {ok, {Inside, _}} -> {ok, Inside}; {error, _} -> {error, nil} end end ); _ -> [] end. -file("src/datadog_query/lint.gleam", 127). ?DOC( " Detects scope-internal artifacts produced by composing `tag_in([])` /\n" " `tag_not_in([])` returning `\"\"`. Returns the offending fragment(s) when\n" " the scope contains a leading/trailing/doubled boolean joiner.\n" "\n" " Examples flagged:\n" " `metric{ AND env:prod}` → [\"leading AND\"]\n" " `metric{env:prod AND }` → [\"trailing AND\"]\n" " `metric{env:prod AND AND service:api}` → [\"doubled joiner\"]\n" ). -spec dangling_joiner(binary()) -> list(binary()). dangling_joiner(Query) -> _pipe = Query, _pipe@1 = extract_scopes(_pipe), gleam@list:flat_map(_pipe@1, fun check_scope_joiners/1). -file("src/datadog_query/lint.gleam", 185). ?DOC( " Detects tag *names* that collide with grammar keywords. Datadog accepts\n" " these tokens as separators, so a tag literally named `IN` produces\n" " confusing parse errors.\n" ). -spec reserved_word_as_tag_name(binary()) -> list(binary()). reserved_word_as_tag_name(Query) -> Reserved = [<<"IN"/utf8>>, <<"NOT"/utf8>>, <<"AND"/utf8>>, <<"OR"/utf8>>, <<"true"/utf8>>, <<"false"/utf8>>], _pipe = Query, _pipe@1 = extract_scopes(_pipe), _pipe@7 = gleam@list:flat_map(_pipe@1, fun(Scope) -> _pipe@2 = Scope, _pipe@3 = gleam@string:replace(_pipe@2, <<"("/utf8>>, <<" "/utf8>>), _pipe@4 = gleam@string:replace(_pipe@3, <<")"/utf8>>, <<" "/utf8>>), _pipe@5 = gleam@string:replace(_pipe@4, <<","/utf8>>, <<" "/utf8>>), _pipe@6 = gleam@string:split(_pipe@5, <<" "/utf8>>), gleam@list:filter_map( _pipe@6, fun(Token) -> case gleam@string:split_once(Token, <<":"/utf8>>) of {ok, {Name, _}} -> case gleam@list:contains(Reserved, Name) of true -> {ok, Name}; false -> {error, nil} end; {error, _} -> {error, nil} end end ) end), gleam@list:unique(_pipe@7). -file("src/datadog_query/lint.gleam", 218). -spec scan_wildcard_quoted(list(binary()), boolean()) -> boolean(). scan_wildcard_quoted(Graphemes, In_string) -> case Graphemes of [] -> false; [G | Rest] -> case {In_string, G} of {true, <<"*"/utf8>>} -> true; {true, <<"\\"/utf8>>} -> case Rest of [_ | Tail] -> scan_wildcard_quoted(Tail, true); [] -> false end; {true, <<"\""/utf8>>} -> scan_wildcard_quoted(Rest, false); {true, _} -> scan_wildcard_quoted(Rest, true); {false, <<"\""/utf8>>} -> scan_wildcard_quoted(Rest, true); {false, _} -> scan_wildcard_quoted(Rest, false) end end. -file("src/datadog_query/lint.gleam", 214). ?DOC( " Detects values where wildcards have been collapsed to literals by\n" " quoting. Datadog matches `*` as a wildcard in bare values but as a\n" " literal asterisk inside `\"...\"`.\n" "\n" " Example: `metric{service:\"foo*\"}` — the `*` is now literal.\n" ). -spec wildcard_in_quoted_value(binary()) -> boolean(). wildcard_in_quoted_value(Query) -> scan_wildcard_quoted(gleam@string:to_graphemes(Query), false). -file("src/datadog_query/lint.gleam", 240). ?DOC( " Detects `metric{} by {tag}` — a legal but suspicious query (a copy-paste\n" " bug that would group a \"match-everything\" set; usually `metric{*}` was\n" " intended).\n" ). -spec empty_scope_with_grouping(binary()) -> boolean(). empty_scope_with_grouping(Query) -> case gleam@string:split_once(Query, <<"{}"/utf8>>) of {ok, {_, After}} -> gleam_stdlib:contains_string(After, <<"by {"/utf8>>); {error, _} -> false end.