@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
package/src/migrate.rs ADDED
@@ -0,0 +1,233 @@
1
+ use std::collections::HashMap;
2
+ use std::fs;
3
+ use std::path::{Path, PathBuf};
4
+
5
+ use ignore::WalkBuilder;
6
+
7
+ use crate::config::{Overrides, discover_config};
8
+
9
+ #[derive(Debug, Clone, PartialEq, Eq)]
10
+ pub enum WriteMode {
11
+ Preview,
12
+ Write,
13
+ }
14
+
15
+ #[derive(Debug, Clone, PartialEq, Eq)]
16
+ pub enum OutputMode {
17
+ SummaryOnly,
18
+ IncludeToml,
19
+ }
20
+
21
+ #[derive(Debug, Clone, PartialEq, Eq)]
22
+ pub enum SourceCleanup {
23
+ Keep,
24
+ Delete,
25
+ RenameSuffix(String),
26
+ }
27
+
28
+ #[derive(Debug, Clone, PartialEq, Eq)]
29
+ pub struct MigrateOptions {
30
+ pub root: PathBuf,
31
+ pub write_mode: WriteMode,
32
+ pub output_mode: OutputMode,
33
+ pub cleanup: SourceCleanup,
34
+ }
35
+
36
+ #[derive(Debug, Clone, PartialEq, Eq)]
37
+ pub struct MigrationEntry {
38
+ pub source: PathBuf,
39
+ pub target: PathBuf,
40
+ pub toml: String,
41
+ }
42
+
43
+ #[derive(Debug, Clone, PartialEq, Eq)]
44
+ pub struct MigrateResult {
45
+ pub entries: Vec<MigrationEntry>,
46
+ pub cleanup_only_sources: Vec<PathBuf>,
47
+ pub warnings: Vec<String>,
48
+ }
49
+
50
+ #[derive(Debug, Default)]
51
+ struct MigrationPlan {
52
+ entries: Vec<MigrationEntry>,
53
+ cleanup_only_sources: Vec<PathBuf>,
54
+ warnings: Vec<String>,
55
+ }
56
+
57
+ /// Apply write + cleanup actions for already planned migration entries.
58
+ ///
59
+ /// # Errors
60
+ /// Returns an error if writing targets or requested source cleanup fails.
61
+ pub fn apply_migration_entries(
62
+ entries: &[MigrationEntry],
63
+ cleanup_only_sources: &[PathBuf],
64
+ cleanup: &SourceCleanup,
65
+ ) -> Result<(), String> {
66
+ let apply_cleanup = |source: &Path| -> Result<(), String> {
67
+ match cleanup {
68
+ SourceCleanup::Keep => {}
69
+ SourceCleanup::Delete => {
70
+ fs::remove_file(source).map_err(|err| {
71
+ format!(
72
+ "failed to delete migrated source config {}: {err}",
73
+ source.display()
74
+ )
75
+ })?;
76
+ }
77
+ SourceCleanup::RenameSuffix(suffix) => {
78
+ let source_name = source.file_name().map_or_else(String::new, |name| {
79
+ name.to_string_lossy().to_string()
80
+ });
81
+ let renamed = source.with_file_name(format!("{source_name}{suffix}"));
82
+ fs::rename(source, &renamed).map_err(|err| {
83
+ format!(
84
+ "failed to rename migrated source config {} to {}: {err}",
85
+ source.display(),
86
+ renamed.display()
87
+ )
88
+ })?;
89
+ }
90
+ }
91
+ Ok(())
92
+ };
93
+
94
+ for entry in entries {
95
+ fs::write(&entry.target, &entry.toml).map_err(|err| {
96
+ format!(
97
+ "failed to write migrated config {}: {err}",
98
+ entry.target.display()
99
+ )
100
+ })?;
101
+ }
102
+
103
+ for source in entries
104
+ .iter()
105
+ .map(|entry| &entry.source)
106
+ .chain(cleanup_only_sources.iter())
107
+ {
108
+ apply_cleanup(source)?;
109
+ }
110
+
111
+ Ok(())
112
+ }
113
+
114
+ fn yaml_config_rank(path: &Path) -> usize {
115
+ match path.file_name().and_then(|name| name.to_str()) {
116
+ Some(".yamllint") => 0,
117
+ Some(".yamllint.yaml") => 1,
118
+ _ => 2,
119
+ }
120
+ }
121
+
122
+ fn is_legacy_yaml_config_path(path: &Path) -> bool {
123
+ path.file_name()
124
+ .and_then(|name| name.to_str())
125
+ .is_some_and(|name| {
126
+ name == ".yamllint" || name == ".yamllint.yaml" || name == ".yamllint.yml"
127
+ })
128
+ }
129
+
130
+ fn discover_legacy_yaml_configs(root: &Path) -> Vec<PathBuf> {
131
+ if root.is_file() {
132
+ return if is_legacy_yaml_config_path(root) {
133
+ vec![root.to_path_buf()]
134
+ } else {
135
+ Vec::new()
136
+ };
137
+ }
138
+
139
+ let walker = WalkBuilder::new(root)
140
+ .hidden(false)
141
+ .ignore(false)
142
+ .git_ignore(false)
143
+ .git_global(false)
144
+ .git_exclude(false)
145
+ .follow_links(false)
146
+ .build();
147
+
148
+ walker
149
+ .flatten()
150
+ .map(|entry| entry.path().to_path_buf())
151
+ .filter(|path| path.is_file() && is_legacy_yaml_config_path(path))
152
+ .collect()
153
+ }
154
+
155
+ fn build_migration_entries(root: &Path) -> Result<MigrationPlan, String> {
156
+ let mut grouped: HashMap<PathBuf, Vec<PathBuf>> = HashMap::new();
157
+ for path in discover_legacy_yaml_configs(root) {
158
+ let parent = path.parent().unwrap_or(Path::new(".")).to_path_buf();
159
+ grouped.entry(parent).or_default().push(path);
160
+ }
161
+
162
+ let mut plan = MigrationPlan::default();
163
+ let mut directories: Vec<PathBuf> = grouped.keys().cloned().collect();
164
+ directories.sort();
165
+
166
+ for dir in directories {
167
+ let mut paths = grouped
168
+ .remove(&dir)
169
+ .expect("directory key should exist in grouped map");
170
+ paths.sort_by(|left, right| {
171
+ yaml_config_rank(left)
172
+ .cmp(&yaml_config_rank(right))
173
+ .then(left.cmp(right))
174
+ });
175
+ let primary = paths
176
+ .first()
177
+ .cloned()
178
+ .expect("at least one config path should exist per grouped directory");
179
+ for ignored in paths.iter().skip(1) {
180
+ plan.cleanup_only_sources.push(ignored.clone());
181
+ plan.warnings.push(format!(
182
+ "warning: skipping lower-precedence config {} in favor of {}",
183
+ ignored.display(),
184
+ primary.display()
185
+ ));
186
+ }
187
+
188
+ let ctx = discover_config(
189
+ &[],
190
+ &Overrides {
191
+ config_file: Some(primary.clone()),
192
+ config_data: None,
193
+ },
194
+ )?;
195
+ let rendered = ctx.config.to_toml_string()?;
196
+ let toml = format!("{}\n", rendered.trim_end());
197
+ plan.entries.push(MigrationEntry {
198
+ source: primary,
199
+ target: dir.join(".ryl.toml"),
200
+ toml,
201
+ });
202
+ }
203
+
204
+ Ok(plan)
205
+ }
206
+
207
+ /// Build and optionally apply YAML-to-TOML config migration.
208
+ ///
209
+ /// # Errors
210
+ /// Returns an error if migration planning fails or file operations fail in write mode.
211
+ pub fn migrate_configs(options: &MigrateOptions) -> Result<MigrateResult, String> {
212
+ if !options.root.exists() {
213
+ return Err(format!(
214
+ "error: migrate root does not exist: {}",
215
+ options.root.display()
216
+ ));
217
+ }
218
+
219
+ let plan = build_migration_entries(&options.root)?;
220
+ if options.write_mode == WriteMode::Write {
221
+ apply_migration_entries(
222
+ &plan.entries,
223
+ &plan.cleanup_only_sources,
224
+ &options.cleanup,
225
+ )?;
226
+ }
227
+
228
+ Ok(MigrateResult {
229
+ entries: plan.entries,
230
+ cleanup_only_sources: plan.cleanup_only_sources,
231
+ warnings: plan.warnings,
232
+ })
233
+ }