@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,119 @@
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 unix_type_reports_crlf() {
20
+ let dir = tempdir().unwrap();
21
+ let file = dir.path().join("crlf.yaml");
22
+ fs::write(&file, "key: value\r\n").unwrap();
23
+ let config = dir.path().join("config.yml");
24
+ fs::write(
25
+ &config,
26
+ "rules:\n document-start: disable\n new-lines:\n type: unix\n",
27
+ )
28
+ .unwrap();
29
+
30
+ let exe = env!("CARGO_BIN_EXE_ryl");
31
+ let (code, stdout, stderr) =
32
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
33
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
34
+ let output = command_output(&stdout, &stderr);
35
+ assert!(
36
+ output.contains("wrong new line character: expected \\n"),
37
+ "missing expected message: {output}"
38
+ );
39
+ assert!(output.contains("new-lines"), "rule id missing: {output}");
40
+ assert!(output.contains("1:11"), "incorrect column: {output}");
41
+ }
42
+
43
+ #[test]
44
+ fn dos_type_reports_lf() {
45
+ let dir = tempdir().unwrap();
46
+ let file = dir.path().join("lf.yaml");
47
+ fs::write(&file, "key: value\n").unwrap();
48
+ let config = dir.path().join("config.yml");
49
+ fs::write(
50
+ &config,
51
+ "rules:\n document-start: disable\n new-lines:\n type: dos\n",
52
+ )
53
+ .unwrap();
54
+
55
+ let exe = env!("CARGO_BIN_EXE_ryl");
56
+ let (code, stdout, stderr) =
57
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
58
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
59
+ let output = command_output(&stdout, &stderr);
60
+ assert!(
61
+ output.contains("wrong new line character: expected \\r\\n"),
62
+ "missing expected message: {output}"
63
+ );
64
+ assert!(output.contains("new-lines"), "rule id missing: {output}");
65
+ }
66
+
67
+ #[test]
68
+ fn dos_type_accepts_crlf() {
69
+ let dir = tempdir().unwrap();
70
+ let file = dir.path().join("ok.yaml");
71
+ fs::write(&file, "key: value\r\n").unwrap();
72
+ let config = dir.path().join("config.yml");
73
+ fs::write(
74
+ &config,
75
+ "rules:\n document-start: disable\n new-lines:\n type: dos\n",
76
+ )
77
+ .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!(code, 0, "expected success: stdout={stdout} stderr={stderr}");
83
+ }
84
+
85
+ #[test]
86
+ fn platform_type_uses_detected_newline() {
87
+ let dir = tempdir().unwrap();
88
+ let file = dir.path().join("platform.yaml");
89
+ let mismatch = if cfg!(windows) {
90
+ "key: value\n"
91
+ } else {
92
+ "key: value\r\n"
93
+ };
94
+ fs::write(&file, mismatch).unwrap();
95
+ let config = dir.path().join("config.yml");
96
+ fs::write(
97
+ &config,
98
+ "rules:\n document-start: disable\n new-lines:\n type: platform\n",
99
+ )
100
+ .unwrap();
101
+
102
+ let exe = env!("CARGO_BIN_EXE_ryl");
103
+ let (code, stdout, stderr) =
104
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
105
+ assert_eq!(code, 1, "expected failure: stdout={stdout} stderr={stderr}");
106
+ let output = command_output(&stdout, &stderr);
107
+ if cfg!(windows) {
108
+ assert!(
109
+ output.contains("wrong new line character: expected \\r\\n"),
110
+ "windows platform expectation mismatch: {output}"
111
+ );
112
+ } else {
113
+ assert!(
114
+ output.contains("wrong new line character: expected \\n"),
115
+ "unix platform expectation mismatch: {output}"
116
+ );
117
+ }
118
+ assert!(output.contains("new-lines"), "rule id missing: {output}");
119
+ }
@@ -0,0 +1,60 @@
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 octal_rule_reports_plain_values() {
20
+ let dir = tempdir().unwrap();
21
+ let file = dir.path().join("values.yaml");
22
+ fs::write(&file, "foo: 010\nbar: 0o10\n").unwrap();
23
+
24
+ let config = dir.path().join("config.yaml");
25
+ fs::write(
26
+ &config,
27
+ "rules:\n document-start: disable\n octal-values: 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!(
35
+ code, 1,
36
+ "expected lint failure: stdout={stdout} stderr={stderr}"
37
+ );
38
+
39
+ let output = command_output(&stdout, &stderr);
40
+ assert!(
41
+ output.contains("forbidden implicit octal value \"010\""),
42
+ "missing implicit message: {output}"
43
+ );
44
+ assert!(
45
+ output.contains("forbidden explicit octal value \"0o10\""),
46
+ "missing explicit message: {output}"
47
+ );
48
+ assert!(
49
+ output.contains("octal-values"),
50
+ "rule label missing: {output}"
51
+ );
52
+ assert!(
53
+ output.contains("1:9"),
54
+ "expected implicit octal position: {output}"
55
+ );
56
+ assert!(
57
+ output.contains("2:10"),
58
+ "expected explicit octal position: {output}"
59
+ );
60
+ }
@@ -0,0 +1,47 @@
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 quoted_strings_reports_redundant_quotes() {
20
+ let dir = tempdir().unwrap();
21
+ let file = dir.path().join("data.yaml");
22
+ fs::write(&file, "foo: \"bar\"\n").unwrap();
23
+
24
+ let config = dir.path().join("config.yaml");
25
+ fs::write(
26
+ &config,
27
+ "rules:\n document-start: disable\n quoted-strings:\n required: only-when-needed\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!(
35
+ code, 1,
36
+ "expected lint failure: stdout={stdout} stderr={stderr}"
37
+ );
38
+ let output = command_output(&stdout, &stderr);
39
+ assert!(
40
+ output.contains("string value is redundantly quoted with any quotes"),
41
+ "missing redundant quote message: {output}"
42
+ );
43
+ assert!(
44
+ output.contains("quoted-strings"),
45
+ "rule label missing: {output}"
46
+ );
47
+ }
@@ -0,0 +1,119 @@
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 project_toml_overrides_yaml_and_emits_single_warning() {
16
+ let td = tempdir().unwrap();
17
+ let root = td.path();
18
+ fs::create_dir_all(root.join("dir")).unwrap();
19
+ fs::write(root.join("dir/a.yaml"), "a: 1\n").unwrap();
20
+ fs::write(root.join("dir/b.yaml"), "b: 2\n").unwrap();
21
+ fs::write(
22
+ root.join(".ryl.toml"),
23
+ "yaml-files = ['**/a.yaml', '**/b.yaml']\n[rules]\nanchors = 'disable'\n",
24
+ )
25
+ .unwrap();
26
+ fs::write(root.join(".yamllint"), "yaml-files: ['**/a.yaml']\n").unwrap();
27
+
28
+ let exe = env!("CARGO_BIN_EXE_ryl");
29
+ let (code, stdout, stderr) = run(Command::new(exe).arg("--list-files").arg(root));
30
+ assert_eq!(code, 0, "expected success: stdout={stdout} stderr={stderr}");
31
+ assert!(stdout.contains("a.yaml"));
32
+ assert!(stdout.contains("b.yaml"));
33
+ let warning_count = stderr
34
+ .matches("warning: ignoring legacy YAML config discovery because TOML config")
35
+ .count();
36
+ assert_eq!(warning_count, 1, "stderr={stderr}");
37
+ }
38
+
39
+ #[test]
40
+ fn explicit_pyproject_without_tool_ryl_errors() {
41
+ let td = tempdir().unwrap();
42
+ let root = td.path();
43
+ let pyproject = root.join("pyproject.toml");
44
+ fs::write(
45
+ &pyproject,
46
+ "[project]\nname = 'demo'\nversion = '0.1.0'\nrequires-python = '>=3.10'\n",
47
+ )
48
+ .unwrap();
49
+ fs::write(root.join("a.yaml"), "a: 1\n").unwrap();
50
+
51
+ let exe = env!("CARGO_BIN_EXE_ryl");
52
+ let (code, _stdout, stderr) = run(Command::new(exe)
53
+ .arg("--list-files")
54
+ .arg("-c")
55
+ .arg(&pyproject)
56
+ .arg(root.join("a.yaml")));
57
+ assert_eq!(code, 2);
58
+ assert!(stderr.contains("missing [tool.ryl] section"));
59
+ }
60
+
61
+ #[test]
62
+ fn global_config_notice_is_emitted_when_env_var_triggers_global_discovery() {
63
+ let td = tempdir().unwrap();
64
+ let root = td.path();
65
+ fs::write(root.join("a.yaml"), "a: 1\n").unwrap();
66
+ fs::write(root.join(".ryl.toml"), "[rules]\nanchors = 'disable'\n").unwrap();
67
+ fs::write(root.join(".yamllint"), "rules: {}\n").unwrap();
68
+
69
+ let exe = env!("CARGO_BIN_EXE_ryl");
70
+ let (code, _stdout, stderr) = run(Command::new(exe)
71
+ .env("YAMLLINT_CONFIG_FILE", "/does/not/exist.yml")
72
+ .arg("--list-files")
73
+ .arg(root.join("a.yaml")));
74
+ assert_eq!(code, 0, "stderr={stderr}");
75
+ assert!(stderr.contains(
76
+ "warning: ignoring legacy YAML config discovery because TOML config"
77
+ ));
78
+ }
79
+
80
+ #[test]
81
+ fn explicit_file_discovery_emits_notice() {
82
+ let td = tempdir().unwrap();
83
+ let root = td.path();
84
+ fs::write(root.join("a.yaml"), "a: 1\n").unwrap();
85
+ fs::write(root.join(".ryl.toml"), "[rules]\nanchors = 'disable'\n").unwrap();
86
+ fs::write(root.join(".yamllint"), "rules: {}\n").unwrap();
87
+
88
+ let exe = env!("CARGO_BIN_EXE_ryl");
89
+ let (code, _stdout, stderr) = run(Command::new(exe)
90
+ .arg("--list-files")
91
+ .arg(root.join("a.yaml")));
92
+ assert_eq!(code, 0, "stderr={stderr}");
93
+ assert!(stderr.contains(
94
+ "warning: ignoring legacy YAML config discovery because TOML config"
95
+ ));
96
+ }
97
+
98
+ #[test]
99
+ fn explicit_notice_is_deduplicated_after_directory_notice() {
100
+ let td = tempdir().unwrap();
101
+ let root = td.path();
102
+ fs::create_dir_all(root.join("one")).unwrap();
103
+ fs::create_dir_all(root.join("two")).unwrap();
104
+ fs::write(root.join("one/from_dir.yaml"), "a: 1\n").unwrap();
105
+ fs::write(root.join("two/from_file.yaml"), "b: 2\n").unwrap();
106
+ fs::write(root.join(".ryl.toml"), "[rules]\nanchors = 'disable'\n").unwrap();
107
+ fs::write(root.join(".yamllint"), "rules: {}\n").unwrap();
108
+
109
+ let exe = env!("CARGO_BIN_EXE_ryl");
110
+ let (code, _stdout, stderr) = run(Command::new(exe)
111
+ .arg("--list-files")
112
+ .arg(root.join("one"))
113
+ .arg(root.join("two/from_file.yaml")));
114
+ assert_eq!(code, 0, "stderr={stderr}");
115
+ let warning_count = stderr
116
+ .matches("warning: ignoring legacy YAML config discovery because TOML config")
117
+ .count();
118
+ assert_eq!(warning_count, 1, "stderr={stderr}");
119
+ }
@@ -0,0 +1,77 @@
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 trailing_spaces_reports_error() {
16
+ let dir = tempdir().unwrap();
17
+ let file = dir.path().join("bad.yaml");
18
+ fs::write(&file, "key: value \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("trailing spaces"),
26
+ "missing message: {output}"
27
+ );
28
+ assert!(
29
+ output.contains("trailing-spaces"),
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, "key: value \n").unwrap();
39
+ let config = dir.path().join("config.yml");
40
+ fs::write(&config, "rules:\n trailing-spaces:\n level: warning\n").unwrap();
41
+
42
+ let exe = env!("CARGO_BIN_EXE_ryl");
43
+ let (code, stdout, stderr) =
44
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
45
+ assert_eq!(
46
+ code, 0,
47
+ "warnings should not fail: stdout={stdout} stderr={stderr}"
48
+ );
49
+ let output = if stderr.is_empty() { &stdout } else { &stderr };
50
+ assert!(
51
+ output.contains("warning"),
52
+ "expected warning output: {output}"
53
+ );
54
+ }
55
+
56
+ #[test]
57
+ fn rule_ignore_skips_file() {
58
+ let dir = tempdir().unwrap();
59
+ let file = dir.path().join("ignored.yaml");
60
+ fs::write(&file, "key: value \n").unwrap();
61
+ let config = dir.path().join("config.yml");
62
+ fs::write(
63
+ &config,
64
+ "rules:\n trailing-spaces:\n ignore:\n - ignored.yaml\n",
65
+ )
66
+ .unwrap();
67
+
68
+ let exe = env!("CARGO_BIN_EXE_ryl");
69
+ let (code, stdout, stderr) =
70
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
71
+ assert_eq!(
72
+ code, 0,
73
+ "ignored file should pass: stdout={stdout} stderr={stderr}"
74
+ );
75
+ assert!(stdout.trim().is_empty(), "expected no stdout: {stdout}");
76
+ assert!(stderr.trim().is_empty(), "expected no stderr: {stderr}");
77
+ }
@@ -0,0 +1,83 @@
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 truthy_rule_reports_plain_truthy_values() {
20
+ let dir = tempdir().unwrap();
21
+ let file = dir.path().join("values.yaml");
22
+ fs::write(&file, "foo: True\nbar: yes\n").unwrap();
23
+
24
+ let config = dir.path().join("config.yaml");
25
+ fs::write(
26
+ &config,
27
+ "rules:\n document-start: disable\n truthy: 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!(
35
+ code, 1,
36
+ "expected lint failure: stdout={stdout} stderr={stderr}"
37
+ );
38
+ let output = command_output(&stdout, &stderr);
39
+ assert!(
40
+ output.contains("truthy value should be one of [false, true]"),
41
+ "missing truthy message: {output}"
42
+ );
43
+ assert!(output.contains("truthy"), "rule label missing: {output}");
44
+ assert!(
45
+ output.contains("1:6"),
46
+ "expected position for value 'True': {output}"
47
+ );
48
+ assert!(
49
+ output.contains("2:6"),
50
+ "expected position for value 'yes': {output}"
51
+ );
52
+ }
53
+
54
+ #[test]
55
+ fn truthy_rule_respects_check_keys_false() {
56
+ let dir = tempdir().unwrap();
57
+ let file = dir.path().join("keys.yaml");
58
+ fs::write(&file, "True: yes\nvalue: True\n").unwrap();
59
+
60
+ let config = dir.path().join("config.yaml");
61
+ fs::write(
62
+ &config,
63
+ "rules:\n document-start: disable\n truthy:\n allowed-values: []\n check-keys: false\n",
64
+ )
65
+ .unwrap();
66
+
67
+ let exe = env!("CARGO_BIN_EXE_ryl");
68
+ let (code, stdout, stderr) =
69
+ run(Command::new(exe).arg("-c").arg(&config).arg(&file));
70
+ assert_eq!(
71
+ code, 1,
72
+ "expected lint failure: stdout={stdout} stderr={stderr}"
73
+ );
74
+ let output = command_output(&stdout, &stderr);
75
+ assert!(
76
+ output.contains("2:8"),
77
+ "value position should be reported when keys are skipped: {output}"
78
+ );
79
+ assert!(
80
+ !output.contains("1:1"),
81
+ "keys should be ignored when disabled: {output}"
82
+ );
83
+ }
@@ -0,0 +1,45 @@
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 command");
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 yaml_files_negation_excludes_files_via_cli() {
16
+ let dir = tempdir().unwrap();
17
+ let root = dir.path();
18
+ let cfg_path = root.join("config.yml");
19
+
20
+ fs::write(
21
+ &cfg_path,
22
+ "rules:\n truthy: enable\nyaml-files: ['*.yaml', '!skip.yaml']\n",
23
+ )
24
+ .unwrap();
25
+ fs::write(root.join("keep.yaml"), "value: Yes\n").unwrap();
26
+ fs::write(root.join("skip.yaml"), "value: Yes\n").unwrap();
27
+
28
+ let exe = env!("CARGO_BIN_EXE_ryl");
29
+ let (code, stdout, stderr) =
30
+ run(Command::new(exe).arg("-c").arg(&cfg_path).arg(root));
31
+
32
+ assert_eq!(
33
+ code, 1,
34
+ "expected lint failure: stdout={stdout} stderr={stderr}"
35
+ );
36
+ let output = if stderr.is_empty() { stdout } else { stderr };
37
+ assert!(
38
+ output.contains("keep.yaml"),
39
+ "expected keep.yaml diagnostics, got:\n{output}"
40
+ );
41
+ assert!(
42
+ !output.contains("skip.yaml"),
43
+ "unexpected diagnostics for skip.yaml:\n{output}"
44
+ );
45
+ }