@owenlamont/ryl 0.4.1

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 (217) hide show
  1. package/.github/CODEOWNERS +1 -0
  2. package/.github/dependabot.yml +13 -0
  3. package/.github/workflows/ci.yml +107 -0
  4. package/.github/workflows/release.yml +613 -0
  5. package/.github/workflows/update_dependencies.yml +61 -0
  6. package/.github/workflows/update_linters.yml +56 -0
  7. package/.pre-commit-config.yaml +87 -0
  8. package/.yamllint +4 -0
  9. package/AGENTS.md +200 -0
  10. package/Cargo.lock +908 -0
  11. package/Cargo.toml +32 -0
  12. package/LICENSE +21 -0
  13. package/README.md +230 -0
  14. package/bin/ryl.js +1 -0
  15. package/clippy.toml +1 -0
  16. package/docs/config-presets.md +100 -0
  17. package/img/benchmark-5x5-5runs.svg +2176 -0
  18. package/package.json +28 -0
  19. package/pyproject.toml +42 -0
  20. package/ruff.toml +107 -0
  21. package/rumdl.toml +20 -0
  22. package/rust-toolchain.toml +3 -0
  23. package/rustfmt.toml +3 -0
  24. package/scripts/benchmark_perf_vs_yamllint.py +400 -0
  25. package/scripts/coverage-missing.ps1 +80 -0
  26. package/scripts/coverage-missing.sh +60 -0
  27. package/src/bin/discover_config_bin.rs +24 -0
  28. package/src/cli_support.rs +33 -0
  29. package/src/conf/mod.rs +85 -0
  30. package/src/config.rs +2099 -0
  31. package/src/decoder.rs +326 -0
  32. package/src/discover.rs +31 -0
  33. package/src/lib.rs +19 -0
  34. package/src/lint.rs +558 -0
  35. package/src/main.rs +535 -0
  36. package/src/migrate.rs +233 -0
  37. package/src/rules/anchors.rs +517 -0
  38. package/src/rules/braces.rs +77 -0
  39. package/src/rules/brackets.rs +77 -0
  40. package/src/rules/colons.rs +475 -0
  41. package/src/rules/commas.rs +372 -0
  42. package/src/rules/comments.rs +299 -0
  43. package/src/rules/comments_indentation.rs +243 -0
  44. package/src/rules/document_end.rs +175 -0
  45. package/src/rules/document_start.rs +84 -0
  46. package/src/rules/empty_lines.rs +152 -0
  47. package/src/rules/empty_values.rs +255 -0
  48. package/src/rules/float_values.rs +259 -0
  49. package/src/rules/flow_collection.rs +562 -0
  50. package/src/rules/hyphens.rs +104 -0
  51. package/src/rules/indentation.rs +803 -0
  52. package/src/rules/key_duplicates.rs +218 -0
  53. package/src/rules/key_ordering.rs +303 -0
  54. package/src/rules/line_length.rs +326 -0
  55. package/src/rules/mod.rs +25 -0
  56. package/src/rules/new_line_at_end_of_file.rs +23 -0
  57. package/src/rules/new_lines.rs +95 -0
  58. package/src/rules/octal_values.rs +121 -0
  59. package/src/rules/quoted_strings.rs +577 -0
  60. package/src/rules/span_utils.rs +37 -0
  61. package/src/rules/trailing_spaces.rs +65 -0
  62. package/src/rules/truthy.rs +420 -0
  63. package/tests/brackets_carriage_return.rs +114 -0
  64. package/tests/build_global_cfg_error.rs +23 -0
  65. package/tests/cli_anchors_rule.rs +143 -0
  66. package/tests/cli_braces_rule.rs +104 -0
  67. package/tests/cli_brackets_rule.rs +104 -0
  68. package/tests/cli_colons_rule.rs +65 -0
  69. package/tests/cli_commas_rule.rs +104 -0
  70. package/tests/cli_comments_indentation_rule.rs +61 -0
  71. package/tests/cli_comments_rule.rs +67 -0
  72. package/tests/cli_config_data_error.rs +30 -0
  73. package/tests/cli_config_flags.rs +66 -0
  74. package/tests/cli_config_migrate.rs +229 -0
  75. package/tests/cli_document_end_rule.rs +92 -0
  76. package/tests/cli_document_start_rule.rs +92 -0
  77. package/tests/cli_empty_lines_rule.rs +87 -0
  78. package/tests/cli_empty_values_rule.rs +68 -0
  79. package/tests/cli_env_config.rs +34 -0
  80. package/tests/cli_exit_and_errors.rs +41 -0
  81. package/tests/cli_file_encoding.rs +203 -0
  82. package/tests/cli_float_values_rule.rs +64 -0
  83. package/tests/cli_format_options.rs +316 -0
  84. package/tests/cli_global_cfg_relaxed.rs +20 -0
  85. package/tests/cli_hyphens_rule.rs +104 -0
  86. package/tests/cli_indentation_rule.rs +65 -0
  87. package/tests/cli_invalid_project_config.rs +39 -0
  88. package/tests/cli_key_duplicates_rule.rs +104 -0
  89. package/tests/cli_key_ordering_rule.rs +59 -0
  90. package/tests/cli_line_length_rule.rs +85 -0
  91. package/tests/cli_list_files.rs +29 -0
  92. package/tests/cli_new_line_rule.rs +141 -0
  93. package/tests/cli_new_lines_rule.rs +119 -0
  94. package/tests/cli_octal_values_rule.rs +60 -0
  95. package/tests/cli_quoted_strings_rule.rs +47 -0
  96. package/tests/cli_toml_config.rs +119 -0
  97. package/tests/cli_trailing_spaces_rule.rs +77 -0
  98. package/tests/cli_truthy_rule.rs +83 -0
  99. package/tests/cli_yaml_files_negation.rs +45 -0
  100. package/tests/colons_rule.rs +303 -0
  101. package/tests/common/compat.rs +114 -0
  102. package/tests/common/fake_env.rs +93 -0
  103. package/tests/common/mod.rs +1 -0
  104. package/tests/conf_builtin.rs +9 -0
  105. package/tests/config_anchors.rs +84 -0
  106. package/tests/config_braces.rs +121 -0
  107. package/tests/config_brackets.rs +127 -0
  108. package/tests/config_commas.rs +79 -0
  109. package/tests/config_comments.rs +65 -0
  110. package/tests/config_comments_indentation.rs +20 -0
  111. package/tests/config_deep_merge_nonstring_key.rs +24 -0
  112. package/tests/config_document_end.rs +54 -0
  113. package/tests/config_document_start.rs +55 -0
  114. package/tests/config_empty_lines.rs +48 -0
  115. package/tests/config_empty_values.rs +35 -0
  116. package/tests/config_env_errors.rs +23 -0
  117. package/tests/config_env_invalid_inline.rs +15 -0
  118. package/tests/config_env_missing.rs +63 -0
  119. package/tests/config_env_shim.rs +301 -0
  120. package/tests/config_explicit_file_parse_error.rs +55 -0
  121. package/tests/config_extended_features.rs +225 -0
  122. package/tests/config_extends_inline.rs +185 -0
  123. package/tests/config_extends_sequence.rs +18 -0
  124. package/tests/config_find_project_home_boundary.rs +54 -0
  125. package/tests/config_find_project_two_files_in_cwd.rs +47 -0
  126. package/tests/config_float_values.rs +34 -0
  127. package/tests/config_from_yaml_paths.rs +32 -0
  128. package/tests/config_hyphens.rs +51 -0
  129. package/tests/config_ignore_errors.rs +243 -0
  130. package/tests/config_ignore_overrides.rs +83 -0
  131. package/tests/config_indentation.rs +65 -0
  132. package/tests/config_invalid_globs.rs +16 -0
  133. package/tests/config_invalid_types.rs +19 -0
  134. package/tests/config_key_duplicates.rs +34 -0
  135. package/tests/config_key_ordering.rs +70 -0
  136. package/tests/config_line_length.rs +65 -0
  137. package/tests/config_locale.rs +111 -0
  138. package/tests/config_merge.rs +26 -0
  139. package/tests/config_new_lines.rs +89 -0
  140. package/tests/config_octal_values.rs +33 -0
  141. package/tests/config_quoted_strings.rs +195 -0
  142. package/tests/config_rule_level.rs +147 -0
  143. package/tests/config_rules_non_string_keys.rs +23 -0
  144. package/tests/config_scalar_overrides.rs +27 -0
  145. package/tests/config_to_toml.rs +110 -0
  146. package/tests/config_toml_coverage.rs +80 -0
  147. package/tests/config_toml_discovery.rs +304 -0
  148. package/tests/config_trailing_spaces.rs +152 -0
  149. package/tests/config_truthy.rs +77 -0
  150. package/tests/config_yaml_files.rs +62 -0
  151. package/tests/config_yaml_files_all_non_string.rs +15 -0
  152. package/tests/config_yaml_files_empty.rs +30 -0
  153. package/tests/coverage_commas.rs +46 -0
  154. package/tests/decoder_decode.rs +338 -0
  155. package/tests/discover_config_bin_all.rs +66 -0
  156. package/tests/discover_config_bin_env_invalid_yaml.rs +26 -0
  157. package/tests/discover_config_bin_project_config_parse_error.rs +24 -0
  158. package/tests/discover_config_bin_user_global_error.rs +26 -0
  159. package/tests/discover_module.rs +30 -0
  160. package/tests/discover_per_file_dir.rs +10 -0
  161. package/tests/discover_per_file_project_config_error.rs +21 -0
  162. package/tests/float_values.rs +43 -0
  163. package/tests/lint_multi_errors.rs +32 -0
  164. package/tests/main_yaml_ok_filtering.rs +30 -0
  165. package/tests/migrate_module.rs +259 -0
  166. package/tests/resolve_ctx_empty_parent.rs +16 -0
  167. package/tests/rule_anchors.rs +442 -0
  168. package/tests/rule_braces.rs +258 -0
  169. package/tests/rule_brackets.rs +217 -0
  170. package/tests/rule_commas.rs +205 -0
  171. package/tests/rule_comments.rs +197 -0
  172. package/tests/rule_comments_indentation.rs +127 -0
  173. package/tests/rule_document_end.rs +118 -0
  174. package/tests/rule_document_start.rs +60 -0
  175. package/tests/rule_empty_lines.rs +96 -0
  176. package/tests/rule_empty_values.rs +102 -0
  177. package/tests/rule_float_values.rs +109 -0
  178. package/tests/rule_hyphens.rs +65 -0
  179. package/tests/rule_indentation.rs +455 -0
  180. package/tests/rule_key_duplicates.rs +76 -0
  181. package/tests/rule_key_ordering.rs +207 -0
  182. package/tests/rule_line_length.rs +200 -0
  183. package/tests/rule_new_lines.rs +51 -0
  184. package/tests/rule_octal_values.rs +53 -0
  185. package/tests/rule_quoted_strings.rs +290 -0
  186. package/tests/rule_trailing_spaces.rs +41 -0
  187. package/tests/rule_truthy.rs +236 -0
  188. package/tests/user_global_invalid_yaml.rs +32 -0
  189. package/tests/yamllint_compat_anchors.rs +280 -0
  190. package/tests/yamllint_compat_braces.rs +411 -0
  191. package/tests/yamllint_compat_brackets.rs +364 -0
  192. package/tests/yamllint_compat_colons.rs +298 -0
  193. package/tests/yamllint_compat_colors.rs +80 -0
  194. package/tests/yamllint_compat_commas.rs +375 -0
  195. package/tests/yamllint_compat_comments.rs +167 -0
  196. package/tests/yamllint_compat_comments_indentation.rs +281 -0
  197. package/tests/yamllint_compat_config.rs +170 -0
  198. package/tests/yamllint_compat_document_end.rs +243 -0
  199. package/tests/yamllint_compat_document_start.rs +136 -0
  200. package/tests/yamllint_compat_empty_lines.rs +117 -0
  201. package/tests/yamllint_compat_empty_values.rs +179 -0
  202. package/tests/yamllint_compat_float_values.rs +216 -0
  203. package/tests/yamllint_compat_hyphens.rs +223 -0
  204. package/tests/yamllint_compat_indentation.rs +398 -0
  205. package/tests/yamllint_compat_key_duplicates.rs +139 -0
  206. package/tests/yamllint_compat_key_ordering.rs +170 -0
  207. package/tests/yamllint_compat_line_length.rs +375 -0
  208. package/tests/yamllint_compat_list.rs +127 -0
  209. package/tests/yamllint_compat_new_line.rs +133 -0
  210. package/tests/yamllint_compat_newline_types.rs +185 -0
  211. package/tests/yamllint_compat_octal_values.rs +172 -0
  212. package/tests/yamllint_compat_quoted_strings.rs +154 -0
  213. package/tests/yamllint_compat_syntax.rs +200 -0
  214. package/tests/yamllint_compat_trailing_spaces.rs +162 -0
  215. package/tests/yamllint_compat_truthy.rs +130 -0
  216. package/tests/yamllint_compat_yaml_files.rs +81 -0
  217. package/typos.toml +2 -0
@@ -0,0 +1,442 @@
1
+ use ryl::rules::anchors::{
2
+ self, Config, MESSAGE_DUPLICATED_ANCHOR, MESSAGE_UNDECLARED_ALIAS,
3
+ MESSAGE_UNUSED_ANCHOR, Violation,
4
+ };
5
+
6
+ fn violation(line: usize, column: usize, message: &str) -> Violation {
7
+ Violation {
8
+ line,
9
+ column,
10
+ message: message.to_string(),
11
+ }
12
+ }
13
+
14
+ #[test]
15
+ fn empty_input_produces_no_diagnostics() {
16
+ let cfg = Config::new_for_tests(true, true, true);
17
+ let hits = anchors::check("", &cfg);
18
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
19
+ }
20
+
21
+ #[test]
22
+ fn handles_windows_line_endings() {
23
+ let cfg = Config::new_for_tests(true, false, false);
24
+ let yaml = "---\r\n- &anchor value\r\n- *anchor\r\n";
25
+ let hits = anchors::check(yaml, &cfg);
26
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
27
+ }
28
+
29
+ #[test]
30
+ fn invalid_anchor_token_is_ignored() {
31
+ let cfg = Config::new_for_tests(true, true, true);
32
+ let yaml = "---\n- & value\n- *missing\n";
33
+ let hits = anchors::check(yaml, &cfg);
34
+ assert_eq!(
35
+ hits,
36
+ vec![violation(
37
+ 3,
38
+ 3,
39
+ &format!(r#"{MESSAGE_UNDECLARED_ALIAS} "missing""#)
40
+ )]
41
+ );
42
+ }
43
+
44
+ #[test]
45
+ fn allows_valid_usage() {
46
+ let cfg = Config::new_for_tests(true, false, false);
47
+ let yaml = "---\n- &anchor value\n- *anchor\n";
48
+ let hits = anchors::check(yaml, &cfg);
49
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
50
+ }
51
+
52
+ #[test]
53
+ fn reports_undeclared_alias() {
54
+ let cfg = Config::new_for_tests(true, false, false);
55
+ let yaml = "---\n- *anchor\n- &anchor value\n";
56
+ let hits = anchors::check(yaml, &cfg);
57
+ assert_eq!(
58
+ hits,
59
+ vec![violation(
60
+ 2,
61
+ 3,
62
+ &format!(r#"{MESSAGE_UNDECLARED_ALIAS} "anchor""#)
63
+ )]
64
+ );
65
+ }
66
+
67
+ #[test]
68
+ fn allows_forward_alias_when_disabled() {
69
+ let cfg = Config::new_for_tests(false, false, false);
70
+ let yaml = "---\n- *anchor\n- &anchor value\n";
71
+ let hits = anchors::check(yaml, &cfg);
72
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
73
+ }
74
+
75
+ #[test]
76
+ fn reports_duplicate_anchor_when_enabled() {
77
+ let cfg = Config::new_for_tests(false, true, false);
78
+ let yaml = "---\n- &anchor first\n- &anchor second\n- *anchor\n";
79
+ let hits = anchors::check(yaml, &cfg);
80
+ assert_eq!(
81
+ hits,
82
+ vec![violation(
83
+ 3,
84
+ 3,
85
+ &format!(r#"{MESSAGE_DUPLICATED_ANCHOR} "anchor""#)
86
+ )]
87
+ );
88
+ }
89
+
90
+ #[test]
91
+ fn reports_unused_anchor() {
92
+ let cfg = Config::new_for_tests(false, false, true);
93
+ let yaml = "---\n- &anchor value\n- 42\n";
94
+ let hits = anchors::check(yaml, &cfg);
95
+ assert_eq!(
96
+ hits,
97
+ vec![violation(
98
+ 2,
99
+ 3,
100
+ &format!(r#"{MESSAGE_UNUSED_ANCHOR} "anchor""#)
101
+ )]
102
+ );
103
+ }
104
+
105
+ #[test]
106
+ fn resets_state_between_documents() {
107
+ let cfg = Config::new_for_tests(true, true, true);
108
+ let yaml = concat!(
109
+ "---\n",
110
+ "- &anchor first\n",
111
+ "- *anchor\n",
112
+ "...\n",
113
+ "---\n",
114
+ "- &anchor second\n",
115
+ "- 1\n"
116
+ );
117
+ let hits = anchors::check(yaml, &cfg);
118
+ assert_eq!(
119
+ hits,
120
+ vec![violation(
121
+ 6,
122
+ 3,
123
+ &format!(r#"{MESSAGE_UNUSED_ANCHOR} "anchor""#)
124
+ )]
125
+ );
126
+ }
127
+
128
+ #[test]
129
+ fn ignores_ampersand_in_strings_and_block_scalars() {
130
+ let cfg = Config::new_for_tests(true, true, true);
131
+ let yaml = concat!(
132
+ "key: \"value &not\"\n",
133
+ "quote: '&still not'\n",
134
+ "literal: |\n",
135
+ " line with &amp\n",
136
+ "folded: >\n",
137
+ " still not &anchor\n",
138
+ "- &real anchor\n",
139
+ "- *real\n",
140
+ );
141
+ let hits = anchors::check(yaml, &cfg);
142
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
143
+ }
144
+
145
+ #[test]
146
+ fn block_scalar_activation_and_release() {
147
+ let cfg = Config::new_for_tests(true, true, true);
148
+ let yaml = concat!(
149
+ "block: |\n",
150
+ "\n",
151
+ " &ignored anchor\n",
152
+ " still inside block\n",
153
+ "next: 1\n",
154
+ "- &real anchor\n",
155
+ "- *real\n",
156
+ );
157
+ let hits = anchors::check(yaml, &cfg);
158
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
159
+ }
160
+
161
+ #[test]
162
+ fn block_scalar_with_explicit_indent_and_chomping() {
163
+ let cfg = Config::new_for_tests(true, true, true);
164
+ let yaml = concat!(
165
+ "literal: |+2\n",
166
+ " &ignored anchor\n",
167
+ " content\n",
168
+ "folded: |-\n",
169
+ " &alsoignored anchor\n",
170
+ "after: value\n",
171
+ "- &real anchor\n",
172
+ "- *real\n",
173
+ );
174
+ let hits = anchors::check(yaml, &cfg);
175
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
176
+ }
177
+
178
+ #[test]
179
+ fn doc_boundary_inside_quotes_ignored() {
180
+ let cfg = Config::new_for_tests(true, true, true);
181
+ let yaml = concat!("---\n", "'---': &anchor value\n", "- *anchor\n",);
182
+ let hits = anchors::check(yaml, &cfg);
183
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
184
+ }
185
+
186
+ #[test]
187
+ fn single_and_double_quote_handling() {
188
+ let cfg = Config::new_for_tests(true, true, true);
189
+ let yaml = concat!(
190
+ "---\n",
191
+ "- \"escaped \\\" quote\"\n",
192
+ "- 'it''s fine'\n",
193
+ "- &anchor value\n",
194
+ "- *anchor\n",
195
+ );
196
+ let hits = anchors::check(yaml, &cfg);
197
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
198
+ }
199
+
200
+ #[test]
201
+ fn comment_stops_scanning() {
202
+ let cfg = Config::new_for_tests(true, true, true);
203
+ let yaml = concat!(
204
+ "---\n",
205
+ "- &anchor value # comment with *alias\n",
206
+ "- *anchor\n",
207
+ );
208
+ let hits = anchors::check(yaml, &cfg);
209
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
210
+ }
211
+
212
+ #[test]
213
+ fn blank_lines_are_ignored() {
214
+ let cfg = Config::new_for_tests(true, true, true);
215
+ let yaml = concat!("---\n", "\n", "\n", "- &anchor value\n", "- *anchor\n",);
216
+ let hits = anchors::check(yaml, &cfg);
217
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
218
+ }
219
+
220
+ #[test]
221
+ fn block_scalar_allows_blank_lines_within_content() {
222
+ let cfg = Config::new_for_tests(true, true, true);
223
+ let yaml = concat!(
224
+ "block: |\n",
225
+ " first\n",
226
+ "\n",
227
+ " second\n",
228
+ "after: value\n",
229
+ "- &anchor value\n",
230
+ "- *anchor\n",
231
+ );
232
+ let hits = anchors::check(yaml, &cfg);
233
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
234
+ }
235
+
236
+ #[test]
237
+ fn nested_block_scalar_handles_outdent() {
238
+ let cfg = Config::new_for_tests(true, true, true);
239
+ let yaml = concat!(
240
+ "outer:\n",
241
+ " inner: |\n",
242
+ " line\n",
243
+ "\n",
244
+ " next: value\n",
245
+ "- &anchor value\n",
246
+ "- *anchor\n",
247
+ );
248
+ let hits = anchors::check(yaml, &cfg);
249
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
250
+ }
251
+
252
+ #[test]
253
+ fn block_scalar_with_zero_indent_indicator() {
254
+ let cfg = Config::new_for_tests(true, true, true);
255
+ let yaml = concat!(
256
+ "literal: |0\n",
257
+ "text\n",
258
+ "- &anchor value\n",
259
+ "- *anchor\n",
260
+ );
261
+ let hits = anchors::check(yaml, &cfg);
262
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
263
+ }
264
+
265
+ #[test]
266
+ fn pipe_in_flow_is_not_block_indicator() {
267
+ let cfg = Config::new_for_tests(true, true, true);
268
+ let yaml = concat!("---\n", "- [|, &anchor value]\n", "- *anchor\n",);
269
+ let hits = anchors::check(yaml, &cfg);
270
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
271
+ }
272
+
273
+ #[test]
274
+ fn alias_token_without_name_is_ignored() {
275
+ let cfg = Config::new_for_tests(true, true, true);
276
+ let yaml = concat!("---\n", "- *\n", "- &anchor value\n", "- *anchor\n",);
277
+ let hits = anchors::check(yaml, &cfg);
278
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
279
+ }
280
+
281
+ #[test]
282
+ fn alias_at_document_start_is_reported() {
283
+ let cfg = Config::new_for_tests(true, false, false);
284
+ let yaml = concat!("---\n", "*missing\n");
285
+ let hits = anchors::check(yaml, &cfg);
286
+ assert_eq!(
287
+ hits,
288
+ vec![violation(
289
+ 2,
290
+ 1,
291
+ &format!(r#"{MESSAGE_UNDECLARED_ALIAS} "missing""#)
292
+ )]
293
+ );
294
+ }
295
+
296
+ #[test]
297
+ fn block_indicator_with_unexpected_suffix_is_not_special() {
298
+ let cfg = Config::new_for_tests(true, true, true);
299
+ let yaml = concat!(
300
+ "value: |x\n",
301
+ " text\n",
302
+ "- &anchor value\n",
303
+ "- *anchor\n",
304
+ );
305
+ let hits = anchors::check(yaml, &cfg);
306
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
307
+ }
308
+
309
+ #[test]
310
+ fn block_indicator_with_spaces_before_indent_value() {
311
+ let cfg = Config::new_for_tests(true, true, true);
312
+ let yaml = concat!(
313
+ "value: | 2\n",
314
+ " text\n",
315
+ "- &anchor value\n",
316
+ "- *anchor\n",
317
+ );
318
+ let hits = anchors::check(yaml, &cfg);
319
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
320
+ }
321
+
322
+ #[test]
323
+ fn block_scalar_dedent_releases_state_immediately() {
324
+ let cfg = Config::new_for_tests(true, true, true);
325
+ let yaml = concat!(
326
+ "block: |\n",
327
+ " inside\n",
328
+ "outdent: &anchor value\n",
329
+ "- *anchor\n",
330
+ );
331
+ let hits = anchors::check(yaml, &cfg);
332
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
333
+ }
334
+
335
+ #[test]
336
+ fn block_scalar_without_indented_content_releases_state() {
337
+ let cfg = Config::new_for_tests(true, true, true);
338
+ let yaml = concat!("block: |\n", "value\n", "- &anchor value\n", "- *anchor\n",);
339
+ let hits = anchors::check(yaml, &cfg);
340
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
341
+ }
342
+
343
+ #[test]
344
+ fn doc_boundary_with_leading_whitespace() {
345
+ let cfg = Config::new_for_tests(false, false, false);
346
+ let yaml = concat!(
347
+ " ---\n",
348
+ "- &anchor value\n",
349
+ " ...\n",
350
+ "---\n",
351
+ "- &other value\n",
352
+ );
353
+ let hits = anchors::check(yaml, &cfg);
354
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
355
+ }
356
+
357
+ #[test]
358
+ fn doc_boundary_with_trailing_whitespace_resets_state() {
359
+ let cfg = Config::new_for_tests(false, false, false);
360
+ let yaml = concat!(
361
+ "--- \n",
362
+ "- &anchor value\n",
363
+ "... \n",
364
+ "--- \n",
365
+ "- &other value\n",
366
+ "- *other\n",
367
+ );
368
+ let hits = anchors::check(yaml, &cfg);
369
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
370
+ }
371
+
372
+ #[test]
373
+ fn doc_boundary_with_comment_is_detected() {
374
+ let cfg = Config::new_for_tests(false, false, false);
375
+ let yaml = concat!(
376
+ "- &anchor value\n",
377
+ "- *anchor\n",
378
+ "--- # next document\n",
379
+ "- &other value\n",
380
+ );
381
+ let hits = anchors::check(yaml, &cfg);
382
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
383
+ }
384
+
385
+ #[test]
386
+ fn partial_doc_marker_is_ignored() {
387
+ let cfg = Config::new_for_tests(true, true, true);
388
+ let yaml = concat!("--\n", "- &anchor value\n", "- *anchor\n",);
389
+ let hits = anchors::check(yaml, &cfg);
390
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
391
+ }
392
+
393
+ #[test]
394
+ fn doc_boundary_detects_plain_markers() {
395
+ let cfg = Config::new_for_tests(false, false, false);
396
+ let yaml = concat!(
397
+ "---\n",
398
+ "- &anchor value\n",
399
+ "...\n",
400
+ "---\n",
401
+ "- &other value\n",
402
+ "- *other\n",
403
+ );
404
+ let hits = anchors::check(yaml, &cfg);
405
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
406
+ }
407
+
408
+ #[test]
409
+ fn doc_boundary_ignored_inside_multiline_single_quote() {
410
+ let cfg = Config::new_for_tests(true, true, true);
411
+ let yaml = concat!(
412
+ "'value\n",
413
+ "---\n",
414
+ "line'\n",
415
+ "- &anchor value\n",
416
+ "- *anchor\n",
417
+ );
418
+ let hits = anchors::check(yaml, &cfg);
419
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
420
+ }
421
+
422
+ #[test]
423
+ fn doc_start_marker_mid_stream_resets_state() {
424
+ let cfg = Config::new_for_tests(true, false, false);
425
+ let yaml = concat!("key: value\n", "---\n", "- &anchor value\n", "- *anchor\n",);
426
+ let hits = anchors::check(yaml, &cfg);
427
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
428
+ }
429
+
430
+ #[test]
431
+ fn block_inconsistent_indent_clears_state() {
432
+ let cfg = Config::new_for_tests(true, true, true);
433
+ let yaml = concat!(
434
+ "block: |\n",
435
+ " first\n",
436
+ " second\n",
437
+ "- &anchor value\n",
438
+ "- *anchor\n",
439
+ );
440
+ let hits = anchors::check(yaml, &cfg);
441
+ assert!(hits.is_empty(), "unexpected diagnostics: {hits:?}");
442
+ }
@@ -0,0 +1,258 @@
1
+ use ryl::rules::braces::{self, Config, Forbid, Violation};
2
+
3
+ fn defaults() -> Config {
4
+ Config::new_for_tests(Forbid::None, 0, 0, -1, -1)
5
+ }
6
+
7
+ #[test]
8
+ fn empty_input_returns_no_diagnostics() {
9
+ let cfg = defaults();
10
+ let diagnostics = braces::check("", &cfg);
11
+ assert!(
12
+ diagnostics.is_empty(),
13
+ "unexpected diagnostics: {diagnostics:?}"
14
+ );
15
+ }
16
+
17
+ #[test]
18
+ fn accepts_compact_flow_mapping() {
19
+ let cfg = defaults();
20
+ let diagnostics = braces::check("object: {key: 1}\n", &cfg);
21
+ assert!(
22
+ diagnostics.is_empty(),
23
+ "unexpected diagnostics: {diagnostics:?}"
24
+ );
25
+ }
26
+
27
+ #[test]
28
+ fn reports_space_after_open_brace() {
29
+ let cfg = defaults();
30
+ let diagnostics = braces::check("object: { key: 1}\n", &cfg);
31
+ assert_eq!(
32
+ diagnostics,
33
+ vec![Violation {
34
+ line: 1,
35
+ column: 10,
36
+ message: "too many spaces inside braces".to_string(),
37
+ }]
38
+ );
39
+ }
40
+
41
+ #[test]
42
+ fn reports_space_before_closing_brace() {
43
+ let cfg = defaults();
44
+ let diagnostics = braces::check("object: {key: 1 }\n", &cfg);
45
+ assert_eq!(
46
+ diagnostics,
47
+ vec![Violation {
48
+ line: 1,
49
+ column: 16,
50
+ message: "too many spaces inside braces".to_string(),
51
+ }]
52
+ );
53
+ }
54
+
55
+ #[test]
56
+ fn forbid_true_rejects_flow_mapping() {
57
+ let cfg = Config::new_for_tests(Forbid::All, 0, 0, -1, -1);
58
+ let diagnostics = braces::check("object: {key: 1}\n", &cfg);
59
+ assert_eq!(
60
+ diagnostics,
61
+ vec![Violation {
62
+ line: 1,
63
+ column: 10,
64
+ message: "forbidden flow mapping".to_string(),
65
+ }]
66
+ );
67
+ }
68
+
69
+ #[test]
70
+ fn forbid_non_empty_allows_empty_mapping() {
71
+ let cfg = Config::new_for_tests(Forbid::NonEmpty, 0, 0, -1, -1);
72
+ let diagnostics = braces::check("object: {}\n", &cfg);
73
+ assert!(
74
+ diagnostics.is_empty(),
75
+ "unexpected diagnostics: {diagnostics:?}"
76
+ );
77
+ }
78
+
79
+ #[test]
80
+ fn forbid_non_empty_rejects_non_empty_mapping() {
81
+ let cfg = Config::new_for_tests(Forbid::NonEmpty, 0, 0, -1, -1);
82
+ let diagnostics = braces::check("object: {key: 1}\n", &cfg);
83
+ assert_eq!(
84
+ diagnostics,
85
+ vec![Violation {
86
+ line: 1,
87
+ column: 10,
88
+ message: "forbidden flow mapping".to_string(),
89
+ }]
90
+ );
91
+ }
92
+
93
+ #[test]
94
+ fn min_spaces_inside_enforced_on_both_sides() {
95
+ let cfg = Config::new_for_tests(Forbid::None, 1, -1, -1, -1);
96
+ let diagnostics = braces::check("object: {key: 1}\n", &cfg);
97
+ assert_eq!(
98
+ diagnostics,
99
+ vec![
100
+ Violation {
101
+ line: 1,
102
+ column: 10,
103
+ message: "too few spaces inside braces".to_string(),
104
+ },
105
+ Violation {
106
+ line: 1,
107
+ column: 16,
108
+ message: "too few spaces inside braces".to_string(),
109
+ },
110
+ ]
111
+ );
112
+ }
113
+
114
+ #[test]
115
+ fn max_spaces_inside_limits_padding() {
116
+ let cfg = Config::new_for_tests(Forbid::None, 0, 1, -1, -1);
117
+ let diagnostics = braces::check("object: { key: 1 }\n", &cfg);
118
+ assert_eq!(
119
+ diagnostics,
120
+ vec![
121
+ Violation {
122
+ line: 1,
123
+ column: 11,
124
+ message: "too many spaces inside braces".to_string(),
125
+ },
126
+ Violation {
127
+ line: 1,
128
+ column: 20,
129
+ message: "too many spaces inside braces".to_string(),
130
+ },
131
+ ]
132
+ );
133
+ }
134
+
135
+ #[test]
136
+ fn empty_mapping_spacing_overrides_defaults() {
137
+ let cfg = Config::new_for_tests(Forbid::None, 0, 0, 1, 2);
138
+ let diagnostics = braces::check("object: {}\n", &cfg);
139
+ assert_eq!(
140
+ diagnostics,
141
+ vec![Violation {
142
+ line: 1,
143
+ column: 10,
144
+ message: "too few spaces inside empty braces".to_string(),
145
+ }]
146
+ );
147
+
148
+ let diagnostics = braces::check("object: { }\n", &cfg);
149
+ assert_eq!(
150
+ diagnostics,
151
+ vec![Violation {
152
+ line: 1,
153
+ column: 13,
154
+ message: "too many spaces inside empty braces".to_string(),
155
+ }]
156
+ );
157
+ }
158
+
159
+ #[test]
160
+ fn multiline_mappings_skip_spacing_checks() {
161
+ let cfg = defaults();
162
+ let diagnostics = braces::check("mapping: {\n key: value\n}\n", &cfg);
163
+ assert!(
164
+ diagnostics.is_empty(),
165
+ "unexpected diagnostics: {diagnostics:?}"
166
+ );
167
+ }
168
+
169
+ #[test]
170
+ fn braces_inside_scalars_are_ignored() {
171
+ let cfg = defaults();
172
+ let diagnostics = braces::check("value: \"{ not a mapping }\"\n", &cfg);
173
+ assert!(
174
+ diagnostics.is_empty(),
175
+ "unexpected diagnostics: {diagnostics:?}"
176
+ );
177
+ }
178
+
179
+ #[test]
180
+ fn comments_inside_flow_mappings_are_ignored() {
181
+ let cfg = defaults();
182
+ let diagnostics =
183
+ braces::check("object: {key: value, # comment\n other: 2}\n", &cfg);
184
+ assert!(
185
+ diagnostics.is_empty(),
186
+ "unexpected diagnostics: {diagnostics:?}"
187
+ );
188
+ }
189
+
190
+ #[test]
191
+ fn carriage_returns_inside_flow_are_ignored() {
192
+ let cfg = defaults();
193
+ let diagnostics = braces::check("object: {key: 1,\r\n other: 2}\r\n", &cfg);
194
+ assert!(
195
+ diagnostics.is_empty(),
196
+ "unexpected diagnostics: {diagnostics:?}"
197
+ );
198
+ }
199
+
200
+ #[test]
201
+ fn nested_mappings_mark_outer_non_empty() {
202
+ let cfg = defaults();
203
+ let diagnostics = braces::check("outer: {{inner: 1}}\n", &cfg);
204
+ assert!(
205
+ diagnostics.is_empty(),
206
+ "unexpected diagnostics: {diagnostics:?}"
207
+ );
208
+ }
209
+
210
+ #[test]
211
+ fn unmatched_closing_brace_is_ignored() {
212
+ let cfg = defaults();
213
+ let diagnostics = braces::check("}\n", &cfg);
214
+ assert!(
215
+ diagnostics.is_empty(),
216
+ "unexpected diagnostics: {diagnostics:?}"
217
+ );
218
+ }
219
+
220
+ #[test]
221
+ fn open_brace_at_end_of_input_is_ignored() {
222
+ let cfg = defaults();
223
+ let diagnostics = braces::check("value: {\n", &cfg);
224
+ assert!(
225
+ diagnostics.is_empty(),
226
+ "unexpected diagnostics: {diagnostics:?}"
227
+ );
228
+ }
229
+
230
+ #[test]
231
+ fn open_brace_with_comment_to_eof_is_ignored() {
232
+ let cfg = defaults();
233
+ let diagnostics = braces::check("value: {# comment\n", &cfg);
234
+ assert!(
235
+ diagnostics.is_empty(),
236
+ "unexpected diagnostics: {diagnostics:?}"
237
+ );
238
+ }
239
+
240
+ #[test]
241
+ fn comment_with_crlf_after_open_brace_is_ignored() {
242
+ let cfg = defaults();
243
+ let diagnostics = braces::check("value: {# comment\r\n", &cfg);
244
+ assert!(
245
+ diagnostics.is_empty(),
246
+ "unexpected diagnostics: {diagnostics:?}"
247
+ );
248
+ }
249
+
250
+ #[test]
251
+ fn double_curly_expression_without_closing_pair_is_ignored() {
252
+ let cfg = defaults();
253
+ let diagnostics = braces::check("value: {{ missing\n", &cfg);
254
+ assert!(
255
+ diagnostics.is_empty(),
256
+ "unexpected diagnostics: {diagnostics:?}"
257
+ );
258
+ }