jarify 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. {jarify-0.3.2 → jarify-0.3.3}/CHANGELOG.md +5 -0
  2. {jarify-0.3.2 → jarify-0.3.3}/PKG-INFO +1 -1
  3. {jarify-0.3.2 → jarify-0.3.3}/pyproject.toml +1 -1
  4. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/formatter.py +10 -1
  5. jarify-0.3.3/tests/fixtures/templates/rust_fmt_trailing_placeholder.expected.sql +7 -0
  6. jarify-0.3.3/tests/fixtures/templates/rust_fmt_trailing_placeholder.input.sql +6 -0
  7. {jarify-0.3.2 → jarify-0.3.3}/tests/test_formatter.py +24 -0
  8. {jarify-0.3.2 → jarify-0.3.3}/uv.lock +1 -1
  9. {jarify-0.3.2 → jarify-0.3.3}/.agents/skills/add-formatter-rule.md +0 -0
  10. {jarify-0.3.2 → jarify-0.3.3}/.github/workflows/ci.yml +0 -0
  11. {jarify-0.3.2 → jarify-0.3.3}/.github/workflows/prepare-release.yml +0 -0
  12. {jarify-0.3.2 → jarify-0.3.3}/.github/workflows/publish.yml +0 -0
  13. {jarify-0.3.2 → jarify-0.3.3}/.gitignore +0 -0
  14. {jarify-0.3.2 → jarify-0.3.3}/.mise/tasks/release/prepare +0 -0
  15. {jarify-0.3.2 → jarify-0.3.3}/.python-version +0 -0
  16. {jarify-0.3.2 → jarify-0.3.3}/AGENTS.md +0 -0
  17. {jarify-0.3.2 → jarify-0.3.3}/LICENSE +0 -0
  18. {jarify-0.3.2 → jarify-0.3.3}/README.md +0 -0
  19. {jarify-0.3.2 → jarify-0.3.3}/cliff.toml +0 -0
  20. {jarify-0.3.2 → jarify-0.3.3}/docs/editors/README.md +0 -0
  21. {jarify-0.3.2 → jarify-0.3.3}/docs/editors/neovim.md +0 -0
  22. {jarify-0.3.2 → jarify-0.3.3}/docs/sql-style-guide.md +0 -0
  23. {jarify-0.3.2 → jarify-0.3.3}/mise.toml +0 -0
  24. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/__init__.py +0 -0
  25. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/cli.py +0 -0
  26. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/config.py +0 -0
  27. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/generator.py +0 -0
  28. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/linter.py +0 -0
  29. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/parser.py +0 -0
  30. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/__init__.py +0 -0
  31. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/base.py +0 -0
  32. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/consistent_empty_array.py +0 -0
  33. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/cte_naming.py +0 -0
  34. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/duckdb_prefer_qualify.py +0 -0
  35. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/duckdb_type_style.py +0 -0
  36. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/keyword_case.py +0 -0
  37. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/no_implicit_cross_join.py +0 -0
  38. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/no_select_star.py +0 -0
  39. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/no_select_star_in_cte.py +0 -0
  40. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/no_unused_cte.py +0 -0
  41. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/prefer_group_by_all.py +0 -0
  42. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/prefer_using_over_on.py +0 -0
  43. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/rules/trailing_commas.py +0 -0
  44. {jarify-0.3.2 → jarify-0.3.3}/src/jarify/types.py +0 -0
  45. {jarify-0.3.2 → jarify-0.3.3}/tests/__init__.py +0 -0
  46. {jarify-0.3.2 → jarify-0.3.3}/tests/conftest.py +0 -0
  47. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/complex/real_world.expected.sql +0 -0
  48. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/complex/real_world.input.sql +0 -0
  49. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/create_table/basic.expected.sql +0 -0
  50. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/create_table/basic.input.sql +0 -0
  51. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/create_table/inline_primary_key.expected.sql +0 -0
  52. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/create_table/inline_primary_key.input.sql +0 -0
  53. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/create_table/simple_no_constraints.expected.sql +0 -0
  54. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/create_table/simple_no_constraints.input.sql +0 -0
  55. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/basic_cte.expected.sql +0 -0
  56. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/basic_cte.input.sql +0 -0
  57. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/cte_inline_comment.expected.sql +0 -0
  58. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/cte_inline_comment.input.sql +0 -0
  59. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/cte_leading_comment.expected.sql +0 -0
  60. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/cte_leading_comment.input.sql +0 -0
  61. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/in_subquery.expected.sql +0 -0
  62. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/cte/in_subquery.input.sql +0 -0
  63. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/agg_window_uppercase.expected.sql +0 -0
  64. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/agg_window_uppercase.input.sql +0 -0
  65. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/create_macro.expected.sql +0 -0
  66. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/create_macro.input.sql +0 -0
  67. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/create_macro_table.expected.sql +0 -0
  68. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/create_macro_table.input.sql +0 -0
  69. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/distinct_in_aggregates.expected.sql +0 -0
  70. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/distinct_in_aggregates.input.sql +0 -0
  71. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/numeric_decimal_type.expected.sql +0 -0
  72. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/numeric_decimal_type.input.sql +0 -0
  73. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/pivot.expected.sql +0 -0
  74. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/pivot.input.sql +0 -0
  75. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/pivot_order_by.expected.sql +0 -0
  76. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/pivot_order_by.input.sql +0 -0
  77. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/qualify.expected.sql +0 -0
  78. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/qualify.input.sql +0 -0
  79. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/read_parquet.expected.sql +0 -0
  80. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/read_parquet.input.sql +0 -0
  81. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/reserved_keyword_type_cast.expected.sql +0 -0
  82. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/reserved_keyword_type_cast.input.sql +0 -0
  83. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/rust_format_syntax.expected.sql +0 -0
  84. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/rust_format_syntax.input.sql +0 -0
  85. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/string_agg.expected.sql +0 -0
  86. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/string_agg.input.sql +0 -0
  87. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/struct_cast_alignment.expected.sql +0 -0
  88. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/struct_cast_alignment.input.sql +0 -0
  89. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/type_cast.expected.sql +0 -0
  90. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/type_cast.input.sql +0 -0
  91. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/values_cte.expected.sql +0 -0
  92. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/duckdb/values_cte.input.sql +0 -0
  93. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/aligned_joins.expected.sql +0 -0
  94. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/aligned_joins.input.sql +0 -0
  95. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/bare_join.expected.sql +0 -0
  96. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/bare_join.input.sql +0 -0
  97. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/comma_join.expected.sql +0 -0
  98. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/comma_join.input.sql +0 -0
  99. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/cross_join_unnest_alias.expected.sql +0 -0
  100. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/cross_join_unnest_alias.input.sql +0 -0
  101. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/multi_join.expected.sql +0 -0
  102. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/multi_join.input.sql +0 -0
  103. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/outer_join_alias_alignment.expected.sql +0 -0
  104. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/outer_join_alias_alignment.input.sql +0 -0
  105. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/unnest_alias_no_as.expected.sql +0 -0
  106. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/joins/unnest_alias_no_as.input.sql +0 -0
  107. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/alias_alignment_rust_fmt_placeholder.expected.sql +0 -0
  108. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/alias_alignment_rust_fmt_placeholder.input.sql +0 -0
  109. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/alias_normalization.expected.sql +0 -0
  110. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/alias_normalization.input.sql +0 -0
  111. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/basic.expected.sql +0 -0
  112. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/basic.input.sql +0 -0
  113. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/between_column_comment.expected.sql +0 -0
  114. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/between_column_comment.input.sql +0 -0
  115. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/boolean_literals.expected.sql +0 -0
  116. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/boolean_literals.input.sql +0 -0
  117. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_and_condition.expected.sql +0 -0
  118. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_and_condition.input.sql +0 -0
  119. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_from_first_subquery.expected.sql +0 -0
  120. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_from_first_subquery.input.sql +0 -0
  121. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_inline.expected.sql +0 -0
  122. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_inline.input.sql +0 -0
  123. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_multiline_threshold.expected.sql +0 -0
  124. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/case_when_multiline_threshold.input.sql +0 -0
  125. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/coalesce_to_ifnull.expected.sql +0 -0
  126. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/coalesce_to_ifnull.input.sql +0 -0
  127. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/from_first.expected.sql +0 -0
  128. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/from_first.input.sql +0 -0
  129. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/from_first_star_qualified.expected.sql +0 -0
  130. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/from_first_star_qualified.input.sql +0 -0
  131. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/from_inline_subquery.expected.sql +0 -0
  132. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/from_inline_subquery.input.sql +0 -0
  133. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/group_by_all.expected.sql +0 -0
  134. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/group_by_all.input.sql +0 -0
  135. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/group_by_all_mixed_expr.expected.sql +0 -0
  136. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/group_by_all_mixed_expr.input.sql +0 -0
  137. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/ifnull.expected.sql +0 -0
  138. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/ifnull.input.sql +0 -0
  139. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/inline_comment.expected.sql +0 -0
  140. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/inline_comment.input.sql +0 -0
  141. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/json_arrow_operators.expected.sql +0 -0
  142. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/json_arrow_operators.input.sql +0 -0
  143. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/leading_comment.expected.sql +0 -0
  144. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/leading_comment.input.sql +0 -0
  145. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/leading_comment_multiline.expected.sql +0 -0
  146. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/leading_comment_multiline.input.sql +0 -0
  147. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/list_contains.expected.sql +0 -0
  148. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/list_contains.input.sql +0 -0
  149. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/paren_arithmetic.expected.sql +0 -0
  150. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/paren_arithmetic.input.sql +0 -0
  151. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_clause.expected.sql +0 -0
  152. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_clause.input.sql +0 -0
  153. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_eq_alignment.expected.sql +0 -0
  154. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_eq_alignment.input.sql +0 -0
  155. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_eq_alignment_nested_subquery.expected.sql +0 -0
  156. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_eq_alignment_nested_subquery.input.sql +0 -0
  157. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_or_inline.expected.sql +0 -0
  158. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/select/where_or_inline.input.sql +0 -0
  159. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/templates/rust_fmt_consecutive_placeholders.expected.sql +0 -0
  160. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/templates/rust_fmt_consecutive_placeholders.input.sql +0 -0
  161. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/templates/rust_fmt_ctas_body_placeholder.expected.sql +0 -0
  162. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/templates/rust_fmt_ctas_body_placeholder.input.sql +0 -0
  163. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/templates/rust_fmt_indented_placeholders.expected.sql +0 -0
  164. {jarify-0.3.2 → jarify-0.3.3}/tests/fixtures/templates/rust_fmt_indented_placeholders.input.sql +0 -0
  165. {jarify-0.3.2 → jarify-0.3.3}/tests/test_config.py +0 -0
  166. {jarify-0.3.2 → jarify-0.3.3}/tests/test_duckdb_rules.py +0 -0
  167. {jarify-0.3.2 → jarify-0.3.3}/tests/test_fixtures.py +0 -0
  168. {jarify-0.3.2 → jarify-0.3.3}/tests/test_lint_rules.py +0 -0
  169. {jarify-0.3.2 → jarify-0.3.3}/tests/test_parser.py +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.3] - 2026-04-27
4
+ ### Bug Fixes
5
+
6
+ - place trailing rust fmt placeholder before statement terminator ([#201](https://github.com/amfaro/jarify/pull/201)) ([#201](https://github.com/amfaro/jarify/pull/201))
7
+
3
8
  ## [0.3.2] - 2026-04-27
4
9
  ### Bug Fixes
5
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarify
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Bespoke SQL linter and formatter for DuckDB, powered by sqlglot
5
5
  Project-URL: Homepage, https://github.com/amfaro/jarify
6
6
  Project-URL: Repository, https://github.com/amfaro/jarify
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "jarify"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "Bespoke SQL linter and formatter for DuckDB, powered by sqlglot"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -88,11 +88,20 @@ def format_sql(
88
88
  tree = _apply_rules(tree, rules)
89
89
  formatted_parts.append(generator.generate(tree))
90
90
 
91
- formatted = "\n;\n\n".join(formatted_parts) + ("\n;\n" if formatted_parts else "")
91
+ # Join statements but hold off on the trailing ;\n -- it must come *after*
92
+ # no-anchor line placeholders are re-inserted, otherwise a trailing
93
+ # {placeholder} ends up after the semicolon instead of before it, which
94
+ # breaks SQL templates that use the placeholder as an optional WHERE clause.
95
+ # A trailing \n is appended to ensure the last line ends with a newline so
96
+ # that no-anchor placeholder re-insertion does not concatenate directly onto
97
+ # the last SQL token without a line break.
98
+ formatted = "\n;\n\n".join(formatted_parts) + ("\n" if formatted_parts else "")
92
99
  formatted = _unmask_rust_fmt_placeholders(formatted, inline_mask)
93
100
  formatted = _unmask_ifnull(formatted)
94
101
  formatted = _unmask_numeric(formatted)
95
102
  result = _reinsert_line_rust_fmt_placeholders(formatted, line_insertions)
103
+ if formatted_parts:
104
+ result = result.rstrip() + "\n;\n"
96
105
  return _restore_ctas_body_placeholders(result, ctas_body_map), warnings
97
106
 
98
107
 
@@ -0,0 +1,7 @@
1
+ SELECT
2
+ request_id
3
+ ,invoice_date
4
+ ,sku_key
5
+ FROM transactions
6
+ {filter}
7
+ ;
@@ -0,0 +1,6 @@
1
+ SELECT
2
+ request_id
3
+ ,invoice_date
4
+ ,sku_key
5
+ FROM transactions
6
+ {filter}
@@ -225,3 +225,27 @@ class TestGroupByPerLine:
225
225
  out, _ = format_sql("SELECT a, count(*) FROM t GROUP BY a")
226
226
  assert "GROUP BY" in out
227
227
  assert "a" in out
228
+
229
+
230
+ class TestTrailingRustFmtPlaceholder:
231
+ """Trailing whole-line Rust format placeholders must land *before* the
232
+ statement-terminating semicolon, not after it.
233
+
234
+ Regression for: formatter inserts ';' between the last SQL clause and a
235
+ trailing {placeholder}, producing invalid SQL when Rust substitutes a
236
+ non-empty value (e.g. 'WHERE seller_key = ...').
237
+ """
238
+
239
+ def test_placeholder_before_semicolon(self):
240
+ sql = "SELECT a, b\nFROM t\n{filter}"
241
+ out, _ = format_sql(sql)
242
+ lines = out.splitlines()
243
+ filter_idx = next(i for i, line in enumerate(lines) if "{filter}" in line)
244
+ semi_idx = next(i for i, line in enumerate(lines) if line.strip() == ";")
245
+ assert filter_idx < semi_idx, f"{{filter}} (line {filter_idx}) must appear before ';' (line {semi_idx}):\n{out}"
246
+
247
+ def test_idempotent(self):
248
+ sql = "SELECT a, b\nFROM t\n{filter}"
249
+ out, _ = format_sql(sql)
250
+ out2, _ = format_sql(out)
251
+ assert out == out2, f"Formatting is not idempotent:\nFirst pass:\n{out}\nSecond pass:\n{out2}"
@@ -34,7 +34,7 @@ wheels = [
34
34
 
35
35
  [[package]]
36
36
  name = "jarify"
37
- version = "0.3.2"
37
+ version = "0.3.3"
38
38
  source = { editable = "." }
39
39
  dependencies = [
40
40
  { name = "click" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes