@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,104 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("process");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ #[test]
15
+ fn hyphens_reports_error() {
16
+ let dir = tempdir().unwrap();
17
+ let file = dir.path().join("bad.yaml");
18
+ fs::write(&file, "---\n- item\n").unwrap();
19
+
20
+ let exe = env!("CARGO_BIN_EXE_ryl");
21
+ let (code, stdout, stderr) = run(Command::new(exe).arg(&file));
22
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
23
+ let output = if stderr.is_empty() { stdout } else { stderr };
24
+ assert!(
25
+ output.contains("too many spaces after hyphen"),
26
+ "missing message: {output}"
27
+ );
28
+ assert!(
29
+ output.contains("hyphens"),
30
+ "rule id missing from output: {output}"
31
+ );
32
+ }
33
+
34
+ #[test]
35
+ fn warning_level_does_not_fail() {
36
+ let dir = tempdir().unwrap();
37
+ let file = dir.path().join("warn.yaml");
38
+ fs::write(&file, "---\n- item\n").unwrap();
39
+ let config = dir.path().join("config.yml");
40
+ fs::write(
41
+ &config,
42
+ "rules:\n document-start: disable\n hyphens:\n level: warning\n",
43
+ )
44
+ .unwrap();
45
+
46
+ let exe = env!("CARGO_BIN_EXE_ryl");
47
+ let (code, stdout, stderr) =
48
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
49
+ assert_eq!(
50
+ code, 0,
51
+ "warnings should not fail: stdout={stdout} stderr={stderr}"
52
+ );
53
+ let output = if stderr.is_empty() { stdout } else { stderr };
54
+ assert!(
55
+ output.contains("warning"),
56
+ "expected warning output: {output}"
57
+ );
58
+ }
59
+
60
+ #[test]
61
+ fn rule_ignore_skips_file() {
62
+ let dir = tempdir().unwrap();
63
+ let file = dir.path().join("ignored.yaml");
64
+ fs::write(&file, "---\n- item\n").unwrap();
65
+ let config = dir.path().join("config.yml");
66
+ fs::write(
67
+ &config,
68
+ "rules:\n document-start: disable\n hyphens:\n ignore:\n - ignored.yaml\n",
69
+ )
70
+ .unwrap();
71
+
72
+ let exe = env!("CARGO_BIN_EXE_ryl");
73
+ let (code, stdout, stderr) =
74
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
75
+ assert_eq!(
76
+ code, 0,
77
+ "ignored file should pass: stdout={stdout} stderr={stderr}"
78
+ );
79
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
80
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
81
+ }
82
+
83
+ #[test]
84
+ fn custom_max_allows_extra_spacing() {
85
+ let dir = tempdir().unwrap();
86
+ let file = dir.path().join("custom.yaml");
87
+ fs::write(&file, "---\n- item\n").unwrap();
88
+ let config = dir.path().join("config.yml");
89
+ fs::write(
90
+ &config,
91
+ "rules:\n document-start: disable\n hyphens:\n max-spaces-after: 3\n",
92
+ )
93
+ .unwrap();
94
+
95
+ let exe = env!("CARGO_BIN_EXE_ryl");
96
+ let (code, stdout, stderr) =
97
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
98
+ assert_eq!(
99
+ code, 0,
100
+ "custom max should pass: stdout={stdout} stderr={stderr}"
101
+ );
102
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
103
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
104
+ }
@@ -0,0 +1,65 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("process");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ #[test]
15
+ fn indentation_reports_error() {
16
+ let dir = tempdir().unwrap();
17
+ let file = dir.path().join("bad.yaml");
18
+ fs::write(&file, "root:\n- item\n").unwrap();
19
+
20
+ let exe = env!("CARGO_BIN_EXE_ryl");
21
+ let (code, stdout, stderr) = run(Command::new(exe).arg(&file));
22
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
23
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
24
+ assert!(output.contains("indentation"), "missing rule id: {output}");
25
+ assert!(
26
+ output.contains("wrong indentation"),
27
+ "missing message: {output}"
28
+ );
29
+ }
30
+
31
+ #[test]
32
+ fn indentation_warning_respected() {
33
+ let dir = tempdir().unwrap();
34
+ let file = dir.path().join("warn.yaml");
35
+ fs::write(&file, "root:\n- item\n").unwrap();
36
+ let config = dir.path().join("config.yml");
37
+ fs::write(&config, "rules:\n indentation:\n level: warning\n").unwrap();
38
+
39
+ let exe = env!("CARGO_BIN_EXE_ryl");
40
+ let (code, stdout, stderr) =
41
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
42
+ assert_eq!(code, 0, "expected warning exit");
43
+ let output = if stderr.is_empty() { stdout } else { stderr };
44
+ assert!(output.contains("warning"), "missing warning line: {output}");
45
+ }
46
+
47
+ #[test]
48
+ fn indentation_sequences_false_skips() {
49
+ let dir = tempdir().unwrap();
50
+ let file = dir.path().join("ok.yaml");
51
+ fs::write(&file, "root:\n- item\n").unwrap();
52
+ let config = dir.path().join("config.yml");
53
+ fs::write(
54
+ &config,
55
+ "rules:\n indentation:\n indent-sequences: false\n",
56
+ )
57
+ .unwrap();
58
+
59
+ let exe = env!("CARGO_BIN_EXE_ryl");
60
+ let (code, stdout, stderr) =
61
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
62
+ assert_eq!(code, 0, "indent-sequences false should pass");
63
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
64
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
65
+ }
@@ -0,0 +1,39 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("failed to run ryl");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ #[test]
15
+ fn invalid_project_config_in_dir_causes_exit_2() {
16
+ let td = tempdir().unwrap();
17
+ let root = td.path();
18
+ fs::create_dir(root.join(".yamllint")).unwrap();
19
+ fs::write(root.join("a.yaml"), "a: 1\n").unwrap();
20
+
21
+ let exe = env!("CARGO_BIN_EXE_ryl");
22
+ let (code, _out, err) = run(Command::new(exe).arg("--list-files").arg(root));
23
+ assert_eq!(code, 2, "expected exit 2: {err}");
24
+ assert!(err.contains("failed to read"));
25
+ }
26
+
27
+ #[test]
28
+ fn invalid_project_config_for_explicit_file_causes_exit_2() {
29
+ let td = tempdir().unwrap();
30
+ let root = td.path();
31
+ fs::create_dir(root.join(".yamllint")).unwrap();
32
+ let f = root.join("a.yaml");
33
+ fs::write(&f, "a: 1\n").unwrap();
34
+
35
+ let exe = env!("CARGO_BIN_EXE_ryl");
36
+ let (code, _out, err) = run(Command::new(exe).arg("--list-files").arg(&f));
37
+ assert_eq!(code, 2, "expected exit 2: {err}");
38
+ assert!(err.contains("failed to read"));
39
+ }
@@ -0,0 +1,104 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("process");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ fn command_output<'a>(stdout: &'a str, stderr: &'a str) -> &'a str {
15
+ if stderr.is_empty() { stdout } else { stderr }
16
+ }
17
+
18
+ #[test]
19
+ fn duplicate_keys_reported() {
20
+ let dir = tempdir().unwrap();
21
+ let file = dir.path().join("dup.yaml");
22
+ fs::write(&file, "foo: 1\nbar: 2\nfoo: 3\n").unwrap();
23
+
24
+ let config = dir.path().join("config.yaml");
25
+ fs::write(
26
+ &config,
27
+ "rules:\n document-start: disable\n key-duplicates: enable\n",
28
+ )
29
+ .unwrap();
30
+
31
+ let exe = env!("CARGO_BIN_EXE_ryl");
32
+ let (code, stdout, stderr) =
33
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
34
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
35
+ let output = command_output(&stdout, &stderr);
36
+ assert!(
37
+ output.contains("duplication of key \"foo\" in mapping"),
38
+ "missing key duplication message: {output}"
39
+ );
40
+ assert!(
41
+ output.contains("key-duplicates"),
42
+ "rule id missing: {output}"
43
+ );
44
+ }
45
+
46
+ #[test]
47
+ fn merge_keys_allowed_by_default() {
48
+ let dir = tempdir().unwrap();
49
+ let file = dir.path().join("merge.yaml");
50
+ fs::write(
51
+ &file,
52
+ "anchor: &a\n value: 1\nmerged:\n <<: *a\n <<: *a\n",
53
+ )
54
+ .unwrap();
55
+
56
+ let config = dir.path().join("config.yaml");
57
+ fs::write(
58
+ &config,
59
+ "rules:\n document-start: disable\n key-duplicates: enable\n",
60
+ )
61
+ .unwrap();
62
+
63
+ let exe = env!("CARGO_BIN_EXE_ryl");
64
+ let (code, stdout, stderr) =
65
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
66
+ assert_eq!(
67
+ code, 0,
68
+ "merge keys allowed by default: stdout={stdout} stderr={stderr}"
69
+ );
70
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
71
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
72
+ }
73
+
74
+ #[test]
75
+ fn merge_keys_forbidden_when_configured() {
76
+ let dir = tempdir().unwrap();
77
+ let file = dir.path().join("merge.yaml");
78
+ fs::write(
79
+ &file,
80
+ "anchor: &a\n value: 1\nmerged:\n <<: *a\n <<: *a\n",
81
+ )
82
+ .unwrap();
83
+
84
+ let config = dir.path().join("config.yaml");
85
+ fs::write(
86
+ &config,
87
+ "rules:\n document-start: disable\n key-duplicates:\n forbid-duplicated-merge-keys: true\n",
88
+ )
89
+ .unwrap();
90
+
91
+ let exe = env!("CARGO_BIN_EXE_ryl");
92
+ let (code, stdout, stderr) =
93
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
94
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
95
+ let output = command_output(&stdout, &stderr);
96
+ assert!(
97
+ output.contains("duplication of key \"<<\" in mapping"),
98
+ "missing merge duplication message: {output}"
99
+ );
100
+ assert!(
101
+ output.contains("key-duplicates"),
102
+ "rule id missing: {output}"
103
+ );
104
+ }
@@ -0,0 +1,59 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("process");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ #[test]
15
+ fn key_ordering_reports_error() {
16
+ let dir = tempdir().unwrap();
17
+ let file = dir.path().join("bad.yaml");
18
+ fs::write(&file, "block mapping:\n second: value\n first: value\n").unwrap();
19
+
20
+ let cfg = dir.path().join("config.yml");
21
+ fs::write(
22
+ &cfg,
23
+ "rules:\n document-start: disable\n key-ordering: enable\n",
24
+ )
25
+ .unwrap();
26
+
27
+ let exe = env!("CARGO_BIN_EXE_ryl");
28
+ let (code, stdout, stderr) = run(Command::new(exe).arg("-c").arg(&cfg).arg(&file));
29
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
30
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
31
+ assert!(
32
+ output.contains("wrong ordering of key \"first\" in mapping"),
33
+ "missing message: {output}"
34
+ );
35
+ assert!(output.contains("key-ordering"), "rule id missing: {output}");
36
+ }
37
+
38
+ #[test]
39
+ fn ignored_keys_skip_enforcement() {
40
+ let dir = tempdir().unwrap();
41
+ let file = dir.path().join("ignored.yaml");
42
+ fs::write(&file, "name: zed\nfirst-name: zed\na: 1\n").unwrap();
43
+
44
+ let cfg = dir.path().join("config.yml");
45
+ fs::write(
46
+ &cfg,
47
+ "rules:\n document-start: disable\n key-ordering:\n ignored-keys: [\"name\", \"first-name\"]\n",
48
+ )
49
+ .unwrap();
50
+
51
+ let exe = env!("CARGO_BIN_EXE_ryl");
52
+ let (code, stdout, stderr) = run(Command::new(exe).arg("-c").arg(&cfg).arg(&file));
53
+ assert_eq!(
54
+ code, 0,
55
+ "ignored keys should not fail: stdout={stdout} stderr={stderr}"
56
+ );
57
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
58
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
59
+ }
@@ -0,0 +1,85 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("process");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ #[test]
15
+ fn line_length_reports_error() {
16
+ let dir = tempdir().unwrap();
17
+ let file = dir.path().join("bad.yaml");
18
+ fs::write(
19
+ &file,
20
+ "key: this line is deliberately far longer than the eighty character default limit so it should fail\n",
21
+ )
22
+ .unwrap();
23
+
24
+ let exe = env!("CARGO_BIN_EXE_ryl");
25
+ let (code, stdout, stderr) = run(Command::new(exe).arg(&file));
26
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
27
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
28
+ assert!(
29
+ output.contains("line too long"),
30
+ "missing message: {output}"
31
+ );
32
+ assert!(
33
+ output.contains("line-length"),
34
+ "rule id missing from output: {output}"
35
+ );
36
+ }
37
+
38
+ #[test]
39
+ fn warning_level_does_not_fail() {
40
+ let dir = tempdir().unwrap();
41
+ let file = dir.path().join("warn.yaml");
42
+ fs::write(&file, format!("{}\n", "W".repeat(40))).unwrap();
43
+ let config = dir.path().join("config.yml");
44
+ fs::write(
45
+ &config,
46
+ "rules:\n line-length:\n max: 20\n allow-non-breakable-words: false\n level: warning\n",
47
+ )
48
+ .unwrap();
49
+
50
+ let exe = env!("CARGO_BIN_EXE_ryl");
51
+ let (code, stdout, stderr) =
52
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
53
+ assert_eq!(
54
+ code, 0,
55
+ "warnings should not fail: stdout={stdout} stderr={stderr}"
56
+ );
57
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
58
+ assert!(
59
+ output.contains("warning"),
60
+ "expected warning output: {output}"
61
+ );
62
+ }
63
+
64
+ #[test]
65
+ fn rule_ignore_skips_file() {
66
+ let dir = tempdir().unwrap();
67
+ let file = dir.path().join("ignored.yaml");
68
+ fs::write(&file, format!("{}\n", "Z".repeat(120))).unwrap();
69
+ let config = dir.path().join("config.yml");
70
+ fs::write(
71
+ &config,
72
+ "rules:\n line-length:\n ignore:\n - ignored.yaml\n",
73
+ )
74
+ .unwrap();
75
+
76
+ let exe = env!("CARGO_BIN_EXE_ryl");
77
+ let (code, stdout, stderr) =
78
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
79
+ assert_eq!(
80
+ code, 0,
81
+ "ignored file should pass: stdout={stdout} stderr={stderr}"
82
+ );
83
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
84
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
85
+ }
@@ -0,0 +1,29 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("process");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ #[test]
15
+ fn list_files_outputs_expected_entries() {
16
+ let dir = tempdir().unwrap();
17
+ let file = dir.path().join("sample.yaml");
18
+ fs::write(&file, "key: value\n").unwrap();
19
+
20
+ let exe = env!("CARGO_BIN_EXE_ryl");
21
+ let (code, stdout, stderr) =
22
+ run(Command::new(exe).arg("--list-files").arg(dir.path()));
23
+ assert_eq!(code, 0, "list-files should succeed: stderr={stderr}");
24
+ assert!(stderr.trim().is_empty(), "unexpected stderr: {stderr}");
25
+ assert!(
26
+ stdout.contains("sample.yaml"),
27
+ "expected stdout to include listed file: {stdout}"
28
+ );
29
+ }
@@ -0,0 +1,141 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use tempfile::tempdir;
5
+
6
+ fn run(cmd: &mut Command) -> (i32, String, String) {
7
+ let out = cmd.output().expect("process");
8
+ let code = out.status.code().unwrap_or(-1);
9
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
10
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
11
+ (code, stdout, stderr)
12
+ }
13
+
14
+ #[test]
15
+ fn missing_newline_reports_error() {
16
+ let dir = tempdir().unwrap();
17
+ let file = dir.path().join("no_newline.yaml");
18
+ fs::write(&file, "key: value").unwrap();
19
+
20
+ let exe = env!("CARGO_BIN_EXE_ryl");
21
+ let (code, stdout, stderr) = run(Command::new(exe).arg(&file));
22
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
23
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
24
+ assert!(
25
+ output.contains("no new line character at the end of file"),
26
+ "missing rule message: {output}"
27
+ );
28
+ assert!(
29
+ output.contains("new-line-at-end-of-file"),
30
+ "rule id missing: {output}"
31
+ );
32
+ }
33
+
34
+ #[test]
35
+ fn newline_present_succeeds() {
36
+ let dir = tempdir().unwrap();
37
+ let file = dir.path().join("ok.yaml");
38
+ fs::write(&file, "key: value\n").unwrap();
39
+
40
+ let exe = env!("CARGO_BIN_EXE_ryl");
41
+ let (code, stdout, stderr) = run(Command::new(exe).arg(&file));
42
+ assert_eq!(code, 0, "expected success: stdout={stdout} stderr={stderr}");
43
+ }
44
+
45
+ #[test]
46
+ fn warning_level_does_not_fail() {
47
+ let dir = tempdir().unwrap();
48
+ let file = dir.path().join("warn.yaml");
49
+ fs::write(&file, "key: value").unwrap();
50
+ let config = dir.path().join("config.yml");
51
+ fs::write(
52
+ &config,
53
+ "rules:\n new-line-at-end-of-file:\n level: warning\n",
54
+ )
55
+ .unwrap();
56
+
57
+ let exe = env!("CARGO_BIN_EXE_ryl");
58
+ let (code, stdout, stderr) =
59
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
60
+ assert_eq!(
61
+ code, 0,
62
+ "warnings should not fail: stdout={stdout} stderr={stderr}"
63
+ );
64
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
65
+ assert!(
66
+ output.contains("warning"),
67
+ "expected warning output: {output}"
68
+ );
69
+ }
70
+
71
+ #[test]
72
+ fn disabled_new_line_rule_allows_success() {
73
+ let dir = tempdir().unwrap();
74
+ let file = dir.path().join("no_newline.yaml");
75
+ fs::write(&file, "key: value").unwrap();
76
+ let config = dir.path().join("config.yml");
77
+ fs::write(&config, "rules:\n new-line-at-end-of-file: disable\n").unwrap();
78
+
79
+ let exe = env!("CARGO_BIN_EXE_ryl");
80
+ let (code, stdout, stderr) =
81
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
82
+ assert_eq!(
83
+ code, 0,
84
+ "disabled rule should pass: stdout={stdout} stderr={stderr}"
85
+ );
86
+ }
87
+
88
+ #[test]
89
+ fn no_warnings_flag_suppresses_output() {
90
+ let dir = tempdir().unwrap();
91
+ let file = dir.path().join("warn.yaml");
92
+ fs::write(&file, "key: value").unwrap();
93
+ let config = dir.path().join("config.yml");
94
+ fs::write(
95
+ &config,
96
+ "rules:\n new-line-at-end-of-file:\n level: warning\n",
97
+ )
98
+ .unwrap();
99
+
100
+ let exe = env!("CARGO_BIN_EXE_ryl");
101
+ let (code, stdout, stderr) = run(Command::new(exe)
102
+ .arg("--no-warnings")
103
+ .arg("-c")
104
+ .arg(&config)
105
+ .arg(&file));
106
+ assert_eq!(
107
+ code, 0,
108
+ "no-warnings should suppress warning exit: stdout={stdout} stderr={stderr}"
109
+ );
110
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
111
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
112
+ }
113
+
114
+ #[test]
115
+ fn strict_mode_with_warning_exits_with_two() {
116
+ let dir = tempdir().unwrap();
117
+ let file = dir.path().join("warn.yaml");
118
+ fs::write(&file, "key: value").unwrap();
119
+ let config = dir.path().join("config.yml");
120
+ fs::write(
121
+ &config,
122
+ "rules:\n new-line-at-end-of-file:\n level: warning\n",
123
+ )
124
+ .unwrap();
125
+
126
+ let exe = env!("CARGO_BIN_EXE_ryl");
127
+ let (code, stdout, stderr) = run(Command::new(exe)
128
+ .arg("--strict")
129
+ .arg("-c")
130
+ .arg(&config)
131
+ .arg(&file));
132
+ assert_eq!(
133
+ code, 2,
134
+ "strict mode should return 2 for warnings: stdout={stdout} stderr={stderr}"
135
+ );
136
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
137
+ assert!(
138
+ output.contains("warning"),
139
+ "expected warning output: {output}"
140
+ );
141
+ }