ruff-sync 0.1.6.dev2__py3-none-any.whl → 0.1.6.dev4__py3-none-any.whl

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.
ruff_sync/cli.py CHANGED
@@ -31,7 +31,9 @@ from ruff_sync.constants import (
31
31
  DEFAULT_BRANCH,
32
32
  DEFAULT_EXCLUDE,
33
33
  DEFAULT_PATH,
34
+ MISSING,
34
35
  ConfKey,
36
+ MissingType,
35
37
  OutputFormat,
36
38
  )
37
39
  from ruff_sync.core import (
@@ -105,11 +107,11 @@ class Arguments(NamedTuple):
105
107
  semantic: bool = False
106
108
  diff: bool = True
107
109
  init: bool = False
108
- pre_commit: bool = False
110
+ pre_commit: bool | MissingType = MISSING
109
111
  save: bool | None = None
110
112
  output_format: OutputFormat = OutputFormat.TEXT
111
- validate: bool = False # run --validate checks before writing
112
- strict: bool = False # treat warnings as errors (implies --validate)
113
+ validate: bool | MissingType = MISSING # run --validate checks before writing
114
+ strict: bool | MissingType = MISSING # treat warnings as errors (implies --validate)
113
115
 
114
116
  @property
115
117
  @deprecated("Use 'to' instead")
@@ -123,6 +125,53 @@ class Arguments(NamedTuple):
123
125
  """Return the set of all field names, including deprecated ones."""
124
126
  return set(cls._fields) | {"source"}
125
127
 
128
+ def resolve(self) -> ExecutionArgs:
129
+ """Resolve all MISSING sentinels to their effective defaults for execution."""
130
+ from ruff_sync.constants import resolve_bool_flags # noqa: PLC0415
131
+
132
+ eff_validate, eff_strict, eff_pre_commit = resolve_bool_flags(
133
+ self.validate,
134
+ self.strict,
135
+ self.pre_commit,
136
+ )
137
+ return ExecutionArgs(
138
+ command=self.command,
139
+ upstream=self.upstream,
140
+ to=self.to,
141
+ exclude=self.exclude,
142
+ verbose=self.verbose,
143
+ branch=self.branch,
144
+ path=self.path,
145
+ semantic=self.semantic,
146
+ diff=self.diff,
147
+ init=self.init,
148
+ pre_commit=eff_pre_commit,
149
+ save=self.save,
150
+ output_format=self.output_format,
151
+ validate=eff_validate,
152
+ strict=eff_strict,
153
+ )
154
+
155
+
156
+ class ExecutionArgs(NamedTuple):
157
+ """Resolved arguments for execution logic — all sentinels replaced with concrete values."""
158
+
159
+ command: str
160
+ upstream: tuple[URL, ...]
161
+ to: pathlib.Path
162
+ exclude: Iterable[str]
163
+ verbose: int
164
+ branch: str
165
+ path: str | None
166
+ semantic: bool
167
+ diff: bool
168
+ init: bool
169
+ pre_commit: bool # plain bool — MISSING resolved to default
170
+ save: bool | None
171
+ output_format: OutputFormat
172
+ validate: bool # plain bool — MISSING resolved to default
173
+ strict: bool # plain bool — MISSING resolved to default
174
+
126
175
 
127
176
  class ResolvedArgs(NamedTuple):
128
177
  """Internal container for resolved arguments."""
@@ -153,8 +202,8 @@ class CLIArguments(Protocol):
153
202
  save: bool | None
154
203
  semantic: bool
155
204
  diff: bool
156
- validate: bool
157
- strict: bool
205
+ validate: bool | None
206
+ strict: bool | None
158
207
 
159
208
 
160
209
  @lru_cache(maxsize=1)
@@ -294,6 +343,24 @@ def _get_cli_parser() -> ArgumentParser:
294
343
  default=None,
295
344
  help="Format for output. Default: text (auto-detected in CI).",
296
345
  )
346
+ common_parser.add_argument(
347
+ "--validate",
348
+ action=BooleanOptionalAction,
349
+ default=None,
350
+ help=(
351
+ "Validate the merged config with Ruff before writing to disk. "
352
+ "Aborts if Ruff rejects the config. Off by default."
353
+ ),
354
+ )
355
+ common_parser.add_argument(
356
+ "--strict",
357
+ action=BooleanOptionalAction,
358
+ default=None,
359
+ help=(
360
+ "Treat all validation warnings as hard failures. Implies --validate. "
361
+ "Aborts if any version mismatch or deprecated rule is detected."
362
+ ),
363
+ )
297
364
 
298
365
  # Pull subcommand (the default action)
299
366
  pull_parser = subparsers.add_parser(
@@ -312,24 +379,6 @@ def _get_cli_parser() -> ArgumentParser:
312
379
  default=None,
313
380
  help="Save CLI arguments to [tool.ruff-sync] in pyproject.toml.",
314
381
  )
315
- pull_parser.add_argument(
316
- "--validate",
317
- action="store_true",
318
- default=False,
319
- help=(
320
- "Validate the merged config with Ruff before writing to disk. "
321
- "Aborts if Ruff rejects the config. Off by default."
322
- ),
323
- )
324
- pull_parser.add_argument(
325
- "--strict",
326
- action="store_true",
327
- default=False,
328
- help=(
329
- "Treat all validation warnings as hard failures. Implies --validate. "
330
- "Aborts if any version mismatch or deprecated rule is detected."
331
- ),
332
- )
333
382
 
334
383
  # Check subcommand
335
384
  check_parser = subparsers.add_parser(
@@ -500,6 +549,44 @@ def _resolve_to(args: CLIArguments, config: Config, initial_to: pathlib.Path) ->
500
549
  return initial_to
501
550
 
502
551
 
552
+ def _resolve_validate(args: CLIArguments, config: Config) -> bool | MissingType:
553
+ """Resolve validation setting from CLI or config.
554
+
555
+ If ``--strict`` was explicitly set at the CLI level, it implies ``--validate``.
556
+ This prevents ``validate = false`` in config from silently neutralising a
557
+ CLI ``--strict`` flag.
558
+ """
559
+ # CLI --validate flag wins if explicitly provided
560
+ if args.validate is not None:
561
+ return args.validate
562
+
563
+ # If --strict was explicitly passed via CLI, it implies validate=True
564
+ # even when config sets `validate = false`.
565
+ if args.strict is True:
566
+ return True
567
+
568
+ # Fallback to config
569
+ validate_attr = ConfKey.to_attr(ConfKey.VALIDATE)
570
+ if validate_attr in config:
571
+ return bool(config[validate_attr]) # type: ignore[literal-required]
572
+
573
+ return MISSING
574
+
575
+
576
+ def _resolve_strict(args: CLIArguments, config: Config) -> bool | MissingType:
577
+ """Resolve strict setting from CLI or config."""
578
+ # CLI flag --strict always wins
579
+ if args.strict is not None:
580
+ return args.strict
581
+
582
+ # Fallback to config
583
+ attr = ConfKey.to_attr(ConfKey.STRICT)
584
+ if attr in config:
585
+ return bool(config[attr]) # type: ignore[literal-required]
586
+
587
+ return MISSING
588
+
589
+
503
590
  def _resolve_args(args: CLIArguments, config: Config, initial_to: pathlib.Path) -> ResolvedArgs:
504
591
  """Resolve upstream, to, exclude, branch, and path from CLI and config."""
505
592
  upstream = _resolve_upstream(args, config)
@@ -522,7 +609,7 @@ def _resolve_args(args: CLIArguments, config: Config, initial_to: pathlib.Path)
522
609
  )
523
610
 
524
611
 
525
- def _resolve_pre_commit(args: CLIArguments, config: Config) -> bool:
612
+ def _resolve_pre_commit(args: CLIArguments, config: Config) -> bool | MissingType:
526
613
  """Resolve pre-commit sync setting from CLI or config."""
527
614
  if args.pre_commit is not None:
528
615
  return args.pre_commit
@@ -531,7 +618,7 @@ def _resolve_pre_commit(args: CLIArguments, config: Config) -> bool:
531
618
  if pre_commit_attr in config:
532
619
  val = config.get(pre_commit_attr)
533
620
  return bool(val)
534
- return False
621
+ return MISSING
535
622
 
536
623
 
537
624
  def _detect_ci_provider() -> CIProvider | None:
@@ -599,28 +686,35 @@ def main() -> int:
599
686
  initial_to = pathlib.Path(arg_to) if arg_to else pathlib.Path()
600
687
  config: Config = get_config(initial_to)
601
688
 
602
- upstream, to_val, exclude, branch, path, output_format = _resolve_args(args, config, initial_to)
603
- pre_commit_val = _resolve_pre_commit(args, config)
689
+ # Resolve all arguments (honoring CLI overrides over config values)
690
+ resolved = _resolve_args(args, config, initial_to)
691
+
692
+ resolved_upstream = tuple(
693
+ resolve_raw_url(u, branch=resolved.branch, path=resolved.path) for u in resolved.upstream
694
+ )
604
695
 
605
- resolved_upstream = tuple(resolve_raw_url(u, branch=branch, path=path) for u in upstream)
696
+ # Resolve validation flags from CLI (with CLI --strict implying validate=True)
697
+ # and fall back to config. Bidirectional implications are handled by
698
+ # resolve_bool_flags() inside Arguments.resolve(), so we store raw sentinels here.
699
+ raw_validate = _resolve_validate(args, config)
700
+ raw_strict = _resolve_strict(args, config)
606
701
 
607
702
  exec_args = Arguments(
608
703
  command=args.command,
609
704
  upstream=resolved_upstream,
610
- to=to_val,
611
- exclude=exclude,
705
+ to=resolved.to,
706
+ exclude=resolved.exclude,
612
707
  verbose=args.verbose,
613
- branch=branch,
614
- path=path,
708
+ branch=resolved.branch,
709
+ path=resolved.path,
615
710
  semantic=getattr(args, "semantic", False),
616
711
  diff=getattr(args, "diff", True),
617
712
  init=getattr(args, "init", False),
618
- pre_commit=pre_commit_val,
713
+ pre_commit=_resolve_pre_commit(args, config),
619
714
  save=getattr(args, "save", None),
620
- output_format=output_format,
621
- # --strict implies --validate
622
- validate=getattr(args, "validate", False) or getattr(args, "strict", False),
623
- strict=getattr(args, "strict", False),
715
+ output_format=resolved.output_format,
716
+ validate=raw_validate,
717
+ strict=raw_strict,
624
718
  )
625
719
 
626
720
  # Warn if the specified output format doesn't match the current CI environment
ruff_sync/constants.py CHANGED
@@ -81,6 +81,8 @@ class ConfKey(str, enum.Enum):
81
81
  INIT = "init"
82
82
  SAVE = "save"
83
83
  VERBOSE = "verbose"
84
+ VALIDATE = "validate"
85
+ STRICT = "strict"
84
86
 
85
87
  # Legacy / Alias Keys
86
88
  SOURCE = "source" # Legacy for 'to'
@@ -127,13 +129,13 @@ class ConfKey(str, enum.Enum):
127
129
 
128
130
  def resolve_defaults(
129
131
  branch: str | MissingType,
130
- path: str | MissingType,
132
+ path: str | None | MissingType,
131
133
  exclude: Iterable[str] | MissingType,
132
134
  ) -> tuple[str, str | None, Iterable[str]]:
133
135
  """Resolve MISSING sentinel values to their effective defaults.
134
136
 
135
- This is the single source of truth for MISSING → default resolution across
136
- the CLI and internal logic, keeping the layers in sync.
137
+ This is the single source of truth for MISSING → default resolution for
138
+ URL-related parameters (branch, path, exclude).
137
139
 
138
140
  Args:
139
141
  branch: The resolved branch value or ``MISSING``.
@@ -151,4 +153,40 @@ def resolve_defaults(
151
153
  # but explicit None is the canonical "root directory" sentinel.
152
154
  eff_path: str | None = raw_path or None
153
155
  eff_exclude = exclude if exclude is not MISSING else DEFAULT_EXCLUDE
156
+
154
157
  return eff_branch, eff_path, eff_exclude
158
+
159
+
160
+ def apply_bool_precedence(
161
+ validate: bool | MissingType, strict: bool | MissingType
162
+ ) -> tuple[bool | MissingType, bool | MissingType]:
163
+ """Apply precedence rules for validate and strict flags.
164
+
165
+ 1. Disabling validation implicitly disables strict mode.
166
+ 2. Strict mode implicitly enables validation.
167
+ """
168
+ if validate is False:
169
+ strict = False
170
+ elif strict is True:
171
+ validate = True
172
+ return validate, strict
173
+
174
+
175
+ def resolve_bool_flags(
176
+ validate: bool | MissingType = MISSING,
177
+ strict: bool | MissingType = MISSING,
178
+ pre_commit: bool | MissingType = MISSING,
179
+ ) -> tuple[bool, bool, bool]:
180
+ """Resolve MISSING sentinel values for boolean execution flags.
181
+
182
+ Returns:
183
+ A ``(validate, strict, pre_commit)`` tuple with defaults applied.
184
+ ``strict=True`` implicitly enables ``validate``.
185
+ """
186
+ validate, strict = apply_bool_precedence(validate, strict)
187
+
188
+ eff_validate = validate if validate is not MISSING else False
189
+ eff_strict = strict if strict is not MISSING else False
190
+ eff_pre_commit = pre_commit if pre_commit is not MISSING else False
191
+
192
+ return eff_validate, eff_strict, eff_pre_commit
ruff_sync/core.py CHANGED
@@ -34,6 +34,7 @@ from ruff_sync.constants import (
34
34
  DEFAULT_BRANCH,
35
35
  DEFAULT_EXCLUDE,
36
36
  DEFAULT_PATH,
37
+ MISSING,
37
38
  ConfKey,
38
39
  )
39
40
  from ruff_sync.formatters import ResultFormatter, get_formatter
@@ -42,7 +43,7 @@ from ruff_sync.pre_commit import sync_pre_commit
42
43
  if TYPE_CHECKING:
43
44
  from tomlkit.container import Container
44
45
 
45
- from ruff_sync.cli import Arguments
46
+ from ruff_sync.cli import Arguments, ExecutionArgs
46
47
 
47
48
  __all__: Final[list[str]] = [
48
49
  "Config",
@@ -123,6 +124,8 @@ class Config(TypedDict, total=False):
123
124
  init: bool
124
125
  pre_commit_version_sync: bool
125
126
  output_format: str
127
+ validate: bool
128
+ strict: bool
126
129
 
127
130
 
128
131
  def _resolve_upstream_target_path(path: str | None) -> str:
@@ -743,7 +746,7 @@ async def fetch_upstreams_concurrently(
743
746
  async def _merge_multiple_upstreams(
744
747
  target_doc: TOMLDocument,
745
748
  is_target_ruff_toml: bool,
746
- args: Arguments,
749
+ args: ExecutionArgs,
747
750
  client: httpx.AsyncClient,
748
751
  ) -> TOMLDocument:
749
752
  """Fetch and merge all upstreams into the target document.
@@ -751,10 +754,8 @@ async def _merge_multiple_upstreams(
751
754
  Downloads are performed concurrently via fetch_upstreams_concurrently,
752
755
  while merging remains sequential to preserve layering order.
753
756
  """
754
- branch, path, exclude = args.branch, args.path, args.exclude
755
-
756
757
  fetch_results = await fetch_upstreams_concurrently(
757
- args.upstream, client, branch=branch, path=path
758
+ args.upstream, client, branch=args.branch, path=args.path
758
759
  )
759
760
 
760
761
  # Sequentially merge the results in the original order
@@ -767,7 +768,7 @@ async def _merge_multiple_upstreams(
767
768
  fetch_result.buffer.read(),
768
769
  is_ruff_toml=is_upstream_ruff_toml,
769
770
  create_if_missing=False,
770
- exclude=exclude,
771
+ exclude=args.exclude,
771
772
  )
772
773
 
773
774
  target_doc = merge_ruff_toml(
@@ -804,14 +805,14 @@ def _print_diff(
804
805
  fmt.diff("".join(diff))
805
806
 
806
807
 
807
- def _check_pre_commit_sync(args: Arguments, fmt: ResultFormatter) -> int | None:
808
+ def _check_pre_commit_sync(pre_commit: bool, fmt: ResultFormatter) -> int | None:
808
809
  """Return exit code 3 if pre-commit hook version is out of sync, otherwise None.
809
810
 
810
811
  Shared helper to avoid duplicating the pre-commit synchronization logic.
811
812
  Exit code 3 is reserved for pre-commit hook drift to avoid collision with
812
813
  argparse (2) and config drift (1).
813
814
  """
814
- if getattr(args, "pre_commit", False) and not sync_pre_commit(pathlib.Path.cwd(), dry_run=True):
815
+ if pre_commit and not sync_pre_commit(pathlib.Path.cwd(), dry_run=True):
815
816
  repo_root = pathlib.Path.cwd()
816
817
  pre_commit_config = repo_root / ".pre-commit-config.yaml"
817
818
  file_path = pre_commit_config if pre_commit_config.exists() else repo_root
@@ -944,6 +945,9 @@ async def check(
944
945
  ... )
945
946
  >>> # asyncio.run(check(args))
946
947
  """
948
+ # Resolve defaults for execution logic
949
+ exec_args = args.resolve()
950
+
947
951
  output_format = args.output_format
948
952
  fmt: ResultFormatter = get_formatter(output_format)
949
953
  try:
@@ -970,7 +974,7 @@ async def check(
970
974
  merged_doc = await _merge_multiple_upstreams(
971
975
  merged_doc,
972
976
  is_target_ruff_toml=is_ruff_toml_file(_source_toml_path.name),
973
- args=args,
977
+ args=exec_args,
974
978
  client=client,
975
979
  )
976
980
 
@@ -991,13 +995,13 @@ async def check(
991
995
 
992
996
  if source_val == merged_val:
993
997
  fmt.success("✅ Ruff configuration is semantically in sync.")
994
- exit_code = _check_pre_commit_sync(args, fmt)
998
+ exit_code = _check_pre_commit_sync(exec_args.pre_commit, fmt)
995
999
  if exit_code is not None:
996
1000
  return exit_code
997
1001
  return 0
998
1002
  elif source_doc.as_string() == merged_doc.as_string():
999
1003
  fmt.success("✅ Ruff configuration is in sync.")
1000
- exit_code = _check_pre_commit_sync(args, fmt)
1004
+ exit_code = _check_pre_commit_sync(exec_args.pre_commit, fmt)
1001
1005
  if exit_code is not None:
1002
1006
  return exit_code
1003
1007
  return 0
@@ -1096,8 +1100,15 @@ def serialize_ruff_sync_config(doc: TOMLDocument, args: Arguments) -> None:
1096
1100
  if args.path != DEFAULT_PATH and args.path is not None:
1097
1101
  ruff_sync_table[ConfKey.PATH] = args.path
1098
1102
 
1099
- if args.pre_commit:
1100
- ruff_sync_table[ConfKey.PRE_COMMIT_VERSION_SYNC] = args.pre_commit
1103
+ # Simple boolean flags
1104
+ for key, attr in [
1105
+ (ConfKey.PRE_COMMIT_VERSION_SYNC, "pre_commit"),
1106
+ (ConfKey.VALIDATE, "validate"),
1107
+ (ConfKey.STRICT, "strict"),
1108
+ ]:
1109
+ val = getattr(args, attr, MISSING)
1110
+ if val is not MISSING:
1111
+ ruff_sync_table[key] = val
1101
1112
 
1102
1113
 
1103
1114
  async def pull(
@@ -1122,6 +1133,9 @@ async def pull(
1122
1133
  ... )
1123
1134
  >>> # asyncio.run(pull(args))
1124
1135
  """
1136
+ # Resolve defaults for execution logic
1137
+ exec_args = args.resolve()
1138
+
1125
1139
  output_format = args.output_format
1126
1140
  fmt = get_formatter(output_format)
1127
1141
  try:
@@ -1154,17 +1168,20 @@ async def pull(
1154
1168
  source_doc = await _merge_multiple_upstreams(
1155
1169
  source_doc,
1156
1170
  is_target_ruff_toml=is_ruff_toml_file(_source_toml_path.name),
1157
- args=args,
1171
+ args=exec_args,
1158
1172
  client=client,
1159
1173
  )
1160
1174
 
1161
1175
  # Validation is opt-in — only run if --validate (or --strict) was passed
1162
- if args.validate:
1176
+ if exec_args.validate:
1163
1177
  from ruff_sync.validation import validate_merged_config # noqa: PLC0415
1164
1178
 
1165
1179
  is_ruff_toml = is_ruff_toml_file(_source_toml_path.name)
1166
1180
  if not validate_merged_config(
1167
- source_doc, is_ruff_toml=is_ruff_toml, strict=args.strict, exclude=args.exclude
1181
+ source_doc,
1182
+ is_ruff_toml=is_ruff_toml,
1183
+ strict=exec_args.strict,
1184
+ exclude=exec_args.exclude,
1168
1185
  ):
1169
1186
  fmt.error(
1170
1187
  "❌ Merged config failed validation. Local file left unchanged.",
@@ -1190,7 +1207,7 @@ async def pull(
1190
1207
  rel_path = _source_toml_path.resolve()
1191
1208
  fmt.success(f"✅ Updated {rel_path}")
1192
1209
 
1193
- if args.pre_commit:
1210
+ if exec_args.pre_commit:
1194
1211
  sync_pre_commit(pathlib.Path.cwd(), dry_run=False)
1195
1212
 
1196
1213
  return 0
ruff_sync/validation.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import functools
6
+ import json
5
7
  import logging
6
8
  import pathlib
7
9
  import re
@@ -109,6 +111,97 @@ def check_python_version_consistency(
109
111
  return True
110
112
 
111
113
 
114
+ _RULE_LIST_KEYS: frozenset[str] = frozenset({"select", "extend-select", "ignore", "extend-ignore"})
115
+
116
+
117
+ @functools.lru_cache(maxsize=1)
118
+ def _get_deprecated_rule_codes() -> frozenset[str]:
119
+ """Return the set of deprecated rule codes reported by the installed Ruff version.
120
+
121
+ Returns an empty frozenset if ruff is not found or output cannot be parsed.
122
+ """
123
+ try:
124
+ result = subprocess.run(
125
+ ["ruff", "rule", "--all", "--output-format", "json"], # noqa: S607
126
+ capture_output=True,
127
+ text=True,
128
+ timeout=30,
129
+ check=False,
130
+ )
131
+ if result.returncode != 0:
132
+ return frozenset()
133
+ rules = json.loads(result.stdout)
134
+ return frozenset(r["code"] for r in rules if r.get("deprecated") is True)
135
+ except (
136
+ FileNotFoundError,
137
+ subprocess.TimeoutExpired,
138
+ json.JSONDecodeError,
139
+ KeyError,
140
+ TypeError,
141
+ ):
142
+ return frozenset()
143
+
144
+
145
+ def check_deprecated_rules(
146
+ doc: TOMLDocument,
147
+ is_ruff_toml: bool = False,
148
+ strict: bool = False,
149
+ _deprecated_codes: frozenset[str] | None = None,
150
+ exclude: Iterable[str] = (),
151
+ ) -> bool:
152
+ """Warn if the merged config references any deprecated Ruff rules.
153
+
154
+ Args:
155
+ doc: The merged TOML document.
156
+ is_ruff_toml: True if the document is a standalone ruff.toml.
157
+ strict: If True, treat deprecated rules as a failure.
158
+ _deprecated_codes: Optional set of deprecated codes to use (for testing).
159
+ exclude: List of keys excluded from the ruff configuration.
160
+
161
+ Returns:
162
+ True if no deprecated rules are found, or if strict=False.
163
+ False if strict=True and deprecated rules are found.
164
+ """
165
+ deprecated_codes = (
166
+ _deprecated_codes if _deprecated_codes is not None else _get_deprecated_rule_codes()
167
+ )
168
+ if not deprecated_codes:
169
+ return True # Nothing to check (ruff not found or returned no deprecated rules)
170
+
171
+ if is_ruff_toml:
172
+ lint_section = doc.get("lint", {})
173
+ else:
174
+ lint_section = doc.get("tool", {}).get("ruff", {}).get("lint", {})
175
+
176
+ if not isinstance(lint_section, dict):
177
+ return True
178
+
179
+ found_deprecated = False
180
+ for key in _RULE_LIST_KEYS:
181
+ full_key = f"lint.{key}" if is_ruff_toml else f"tool.ruff.lint.{key}"
182
+ if key in exclude or full_key in exclude:
183
+ continue
184
+
185
+ rule_list = lint_section.get(key, [])
186
+ if not isinstance(rule_list, list):
187
+ continue
188
+ for rule_code in rule_list:
189
+ rule_str = str(rule_code).strip().upper()
190
+ if rule_str in deprecated_codes:
191
+ found_deprecated = True
192
+ msg = (
193
+ f"Upstream config uses deprecated rule '{rule_str}' "
194
+ f"(found in [{'lint' if is_ruff_toml else 'tool.ruff.lint'}].{key}). "
195
+ "This rule may be removed in a future Ruff version."
196
+ )
197
+ if strict:
198
+ LOGGER.error(f"❌ {msg}")
199
+ else:
200
+ LOGGER.warning(f"⚠️ {msg}")
201
+
202
+ return not (strict and found_deprecated)
203
+
204
+
112
205
  def validate_toml_syntax(doc: TOMLDocument) -> bool:
113
206
  """Return True if the document serializes to valid TOML.
114
207
 
@@ -218,6 +311,13 @@ def validate_merged_config(
218
311
  return False
219
312
  if not validate_ruff_accepts_config(doc, is_ruff_toml=is_ruff_toml, strict=strict):
220
313
  return False
314
+
315
+ version_ok = True
221
316
  if not is_ruff_toml:
222
- return check_python_version_consistency(doc, strict=strict, exclude=exclude)
223
- return True
317
+ version_ok = check_python_version_consistency(doc, strict=strict, exclude=exclude)
318
+
319
+ deprecated_ok = check_deprecated_rules(
320
+ doc, is_ruff_toml=is_ruff_toml, strict=strict, exclude=exclude
321
+ )
322
+
323
+ return version_ok and deprecated_ok
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ruff-sync
3
- Version: 0.1.6.dev2
3
+ Version: 0.1.6.dev4
4
4
  Summary: Synchronize Ruff linter configuration across projects
5
5
  Project-URL: Homepage, https://github.com/Kilo59/ruff-sync
6
6
  Project-URL: Documentation, https://kilo59.github.io/ruff-sync/
@@ -1,15 +1,15 @@
1
1
  ruff_sync/__init__.py,sha256=M_1YK0BcPzlLlxlBOEPEBTCkAVm37r-yoQzGGZnfQ7Y,1206
2
2
  ruff_sync/__main__.py,sha256=oMIbv9ywToleBKJrZ-GMo_KhM4lHg02nJe7t6neWzJE,179
3
- ruff_sync/cli.py,sha256=HaDQgSB9TpPetuFBcc8_KAPjMVoJkaPxRVsX5uLkn6g,22193
3
+ ruff_sync/cli.py,sha256=ieZVvMD2AlaHWSfXLidQhCRD87VmzcqV6J50tjnNRuc,25345
4
4
  ruff_sync/config_io.py,sha256=3GlFjFlii_hBLaNnUJRfFI0iozkngTBbfqu4rLrpZKQ,4348
5
- ruff_sync/constants.py,sha256=yE6ORJaErY8Bz5KTDDk7kRKLOlJ-xgS7Gvz_mFpjDJY,4604
6
- ruff_sync/core.py,sha256=R9IhMGlBAwrylNqZ7JpdffmjOj-A_ssZL9ViPFwcr5A,43819
5
+ ruff_sync/constants.py,sha256=g7ZU8mGdQ2682tKQuKkhRaTRRmg0K2vMVp3Ljs60D70,5822
6
+ ruff_sync/core.py,sha256=H2qJ_fn887ijlBh1Q4gh_24VHpzOqRjv5fvsNFhS0r0,44278
7
7
  ruff_sync/dependencies.py,sha256=RpJXAsrB-Y-ol_ZH4U92XIuyq8_bWaYwp9sn9KpxraA,2096
8
8
  ruff_sync/formatters.py,sha256=A4p2XAyc-xnteUmdtRXbp63fIjT9MdyKF6qKvcyNMho,29908
9
9
  ruff_sync/pre_commit.py,sha256=0ltXUkmocbrSwwf6cPbNc_K5K9n-gvou98xJy9YUhr0,5303
10
10
  ruff_sync/system.py,sha256=vF9Jjb-Z9MuLwo3wiOqcCDxniNtjvo3O-aFt4NJf-2Y,5715
11
11
  ruff_sync/types_.py,sha256=BejR6sgctR0qr5fKgRTtgHp2TqnvT8zTRo1NJi2VHLo,824
12
- ruff_sync/validation.py,sha256=6gSI17TSdgJsPx3VyCJPtOn6ME6fIWOj9UgU7xI-5nA,8223
12
+ ruff_sync/validation.py,sha256=OatdkWUKEyxw7CYTOFNpH6IRsbVfU5EHjt6Lu6nOO9U,11585
13
13
  ruff_sync/tui/__init__.py,sha256=DcwvE70ETjBcC6pQeHpjrYtkJp8z4StGM8L3EuaVcIo,635
14
14
  ruff_sync/tui/app.py,sha256=soq0fkFsLeB7ZxJWNZM_H6bNj7H8p6nEYyirVcskN54,9317
15
15
  ruff_sync/tui/constants.py,sha256=fEPyp2k817QLluyiVHqDObzDONlHz_L3Ec0SYAUzzMs,267
@@ -17,8 +17,8 @@ ruff_sync/tui/screens.py,sha256=NVIlqNIr64JrFFWeec7usqJCb1x41NEmSfRZGeEcwuo,5557
17
17
  ruff_sync/tui/themes.py,sha256=-x9u_8iZPa1XZuo8O_tWVHyCImN9Qs9F7xPhl0VLwZw,654
18
18
  ruff_sync/tui/types_.py,sha256=poNUBJotgUso2IkUi3nXUuxz6wv1goFWZimj4VttdJE,8271
19
19
  ruff_sync/tui/widgets.py,sha256=2m3gyH4xDBpM0CK-5eQmmUCZXYKJujrbyVkFTxqmHcg,9760
20
- ruff_sync-0.1.6.dev2.dist-info/METADATA,sha256=gsszzmgDXK05MeDAx6lH1vNB1Lei-8em2NZSWSAwc04,22706
21
- ruff_sync-0.1.6.dev2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
22
- ruff_sync-0.1.6.dev2.dist-info/entry_points.txt,sha256=QQ0pjxMIN0vp5EmNFkyb-1Yk8JOIkid8zAPN3IpAExA,78
23
- ruff_sync-0.1.6.dev2.dist-info/licenses/LICENSE.md,sha256=RcCP8t8xyCx2cvMZSLjBrPqJfctciWC6q85ceQAcEfA,1080
24
- ruff_sync-0.1.6.dev2.dist-info/RECORD,,
20
+ ruff_sync-0.1.6.dev4.dist-info/METADATA,sha256=8Z7T_-halUxzrgamLZkseVji5ARHpLJMdYxPo8pg6HM,22706
21
+ ruff_sync-0.1.6.dev4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
22
+ ruff_sync-0.1.6.dev4.dist-info/entry_points.txt,sha256=QQ0pjxMIN0vp5EmNFkyb-1Yk8JOIkid8zAPN3IpAExA,78
23
+ ruff_sync-0.1.6.dev4.dist-info/licenses/LICENSE.md,sha256=RcCP8t8xyCx2cvMZSLjBrPqJfctciWC6q85ceQAcEfA,1080
24
+ ruff_sync-0.1.6.dev4.dist-info/RECORD,,