code-standards 7.0.0__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.
Files changed (99) hide show
  1. code_standards-7.0.0.dist-info/METADATA +53 -0
  2. code_standards-7.0.0.dist-info/RECORD +99 -0
  3. code_standards-7.0.0.dist-info/WHEEL +4 -0
  4. code_standards-7.0.0.dist-info/entry_points.txt +3 -0
  5. code_standards-7.0.0.dist-info/licenses/LICENSE +21 -0
  6. sarj_standards/__init__.py +30 -0
  7. sarj_standards/__main__.py +5 -0
  8. sarj_standards/_meta.py +22 -0
  9. sarj_standards/api.py +890 -0
  10. sarj_standards/cli/__init__.py +0 -0
  11. sarj_standards/cli/main.py +2466 -0
  12. sarj_standards/configs/cli-reference.v1.json +1 -0
  13. sarj_standards/configs/doctor.config.json +22 -0
  14. sarj_standards/configs/eslint.application.mjs +1366 -0
  15. sarj_standards/configs/eslint.peers.json +44 -0
  16. sarj_standards/configs/eslint.strict.mjs +1060 -0
  17. sarj_standards/configs/markdownlint.strict.yaml +12 -0
  18. sarj_standards/configs/pyright.strict.json +96 -0
  19. sarj_standards/configs/ruff.application.toml +363 -0
  20. sarj_standards/configs/ruff.strict.toml +338 -0
  21. sarj_standards/configs/rule-inventory.v1.json +1 -0
  22. sarj_standards/configs/rule-ledger.json +846 -0
  23. sarj_standards/configs/rule-warning-levels.v1.json +1 -0
  24. sarj_standards/configs/taplo.strict.toml +14 -0
  25. sarj_standards/configs/yamllint.strict.yaml +25 -0
  26. sarj_standards/libs/__init__.py +0 -0
  27. sarj_standards/libs/adoption/__init__.py +0 -0
  28. sarj_standards/libs/adoption/configs.py +36 -0
  29. sarj_standards/libs/adoption/doctor.py +1346 -0
  30. sarj_standards/libs/adoption/exclusions.py +66 -0
  31. sarj_standards/libs/adoption/hooks.py +423 -0
  32. sarj_standards/libs/adoption/launcher.py +240 -0
  33. sarj_standards/libs/adoption/lifecycle.py +493 -0
  34. sarj_standards/libs/adoption/manifest.py +550 -0
  35. sarj_standards/libs/adoption/packagemanager.py +285 -0
  36. sarj_standards/libs/adoption/retired_suppressions.py +371 -0
  37. sarj_standards/libs/adoption/scaffold.py +1660 -0
  38. sarj_standards/libs/adoption/service.py +441 -0
  39. sarj_standards/libs/adoption/transaction.py +274 -0
  40. sarj_standards/libs/adoption/upgrade.py +516 -0
  41. sarj_standards/libs/adoption/uvtool.py +62 -0
  42. sarj_standards/libs/catalogs/__init__.py +9 -0
  43. sarj_standards/libs/catalogs/slack_automations.py +627 -0
  44. sarj_standards/libs/corpus/__init__.py +25 -0
  45. sarj_standards/libs/corpus/manifest.py +211 -0
  46. sarj_standards/libs/corpus/snapshot.py +222 -0
  47. sarj_standards/libs/diagnostics/__init__.py +65 -0
  48. sarj_standards/libs/diagnostics/analysis.schema.json +161 -0
  49. sarj_standards/libs/diagnostics/baseline.py +131 -0
  50. sarj_standards/libs/diagnostics/models.py +574 -0
  51. sarj_standards/libs/diagnostics/serialize.py +290 -0
  52. sarj_standards/libs/diagnostics/source.py +172 -0
  53. sarj_standards/libs/filesystem.py +11 -0
  54. sarj_standards/libs/linting/__init__.py +0 -0
  55. sarj_standards/libs/linting/analysis.py +422 -0
  56. sarj_standards/libs/linting/external.py +1454 -0
  57. sarj_standards/libs/linting/library_policy.py +688 -0
  58. sarj_standards/libs/linting/policy.py +152 -0
  59. sarj_standards/libs/linting/runner.py +442 -0
  60. sarj_standards/libs/linting/textlint.py +1605 -0
  61. sarj_standards/libs/release/__init__.py +98 -0
  62. sarj_standards/libs/release/_values.py +24 -0
  63. sarj_standards/libs/release/artifacts.py +191 -0
  64. sarj_standards/libs/release/causality.py +80 -0
  65. sarj_standards/libs/release/changes.py +48 -0
  66. sarj_standards/libs/release/process.py +128 -0
  67. sarj_standards/libs/release/publish.py +85 -0
  68. sarj_standards/libs/release/registry.py +271 -0
  69. sarj_standards/libs/release/release_age.py +218 -0
  70. sarj_standards/libs/release/rollout.py +1163 -0
  71. sarj_standards/libs/release/tags.py +373 -0
  72. sarj_standards/libs/release/typescript.py +191 -0
  73. sarj_standards/libs/repository/__init__.py +0 -0
  74. sarj_standards/libs/repository/cli_reference_artifact.py +324 -0
  75. sarj_standards/libs/repository/comment_corpus.py +536 -0
  76. sarj_standards/libs/repository/config_generation.py +146 -0
  77. sarj_standards/libs/repository/docs.py +347 -0
  78. sarj_standards/libs/repository/hooks.py +118 -0
  79. sarj_standards/libs/repository/ledger.py +99 -0
  80. sarj_standards/libs/repository/repository.py +744 -0
  81. sarj_standards/libs/repository/rule_authoring.py +246 -0
  82. sarj_standards/libs/repository/rule_catalog_artifact.py +479 -0
  83. sarj_standards/libs/repository/rule_changes.py +318 -0
  84. sarj_standards/libs/repository/rule_inventory_artifact.py +142 -0
  85. sarj_standards/libs/repository/rule_lifecycle.py +167 -0
  86. sarj_standards/libs/repository/rule_maintenance.py +225 -0
  87. sarj_standards/libs/rules/__init__.py +74 -0
  88. sarj_standards/libs/rules/catalog.py +145 -0
  89. sarj_standards/libs/rules/contracts.py +382 -0
  90. sarj_standards/libs/rules/corpus_runner.py +365 -0
  91. sarj_standards/libs/rules/evaluation.py +177 -0
  92. sarj_standards/libs/setup/__init__.py +4 -0
  93. sarj_standards/libs/setup/repository.py +40 -0
  94. sarj_standards/py.typed +0 -0
  95. sarj_standards/schemas/__init__.py +4 -0
  96. sarj_standards/schemas/_paths.py +7 -0
  97. sarj_standards/schemas/rule-catalog.v1.json +1 -0
  98. sarj_standards/schemas/rule-catalog.v1.schema.json +112 -0
  99. sarj_standards/schemas/slack-automations.v1.schema.json +1751 -0
@@ -0,0 +1,246 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ import re
6
+ from types import MappingProxyType
7
+ from typing import TYPE_CHECKING
8
+
9
+ from sarj_standards.libs.adoption import transaction
10
+ from sarj_standards.libs.rules import RuleEngine, RuleSelector
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Iterable
15
+
16
+
17
+ _CODE = re.compile(r"SARJ(?P<number>[0-9]{3})")
18
+ _BANDS = MappingProxyType(
19
+ {RuleEngine.PYTHON: range(400, 500), RuleEngine.SQL: range(100, 200), RuleEngine.IAC: range(200, 300)}
20
+ )
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class AuthoringPlan:
25
+ selector: RuleSelector
26
+ code: str | None
27
+ files: tuple[tuple[Path, str], ...]
28
+
29
+ def render(self, root: Path) -> str:
30
+ lines = [f"rule: {self.selector}", f"code: {self.code or 'engine-native'}"]
31
+ lines.extend(f"create: {path.relative_to(root)}" for path, _ in self.files)
32
+ registry = _registry_path(root, self.selector.engine).relative_to(root)
33
+ lines.extend(
34
+ (
35
+ f"next: implement the TODOs and register the rule in {registry}",
36
+ f"then: run the focused test and `code-standards maintain rules verify {self.selector}`",
37
+ f"then: run `code-standards maintain rules evaluate --rule {self.selector} --scope corpus`",
38
+ f"finally: run `code-standards maintain rules prepare {self.selector}`",
39
+ )
40
+ )
41
+ return "\n".join(lines)
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class VerificationResult:
46
+ status: int
47
+ message: str
48
+
49
+
50
+ def verify(root: Path, selector: RuleSelector) -> VerificationResult:
51
+ from sarj_standards.libs.repository import ( # ruff: ignore[import-outside-top-level] -- expensive engines load on demand
52
+ rule_catalog_artifact,
53
+ )
54
+
55
+ repository = root.resolve()
56
+ catalog = rule_catalog_artifact.build(repository)
57
+ documented = next((item for item in catalog.rules if item.spec.key == str(selector)), None)
58
+ if documented is None:
59
+ msg = f"unknown live rule selector: {selector}; register the implementation, then rerun verify"
60
+ return VerificationResult(2, msg)
61
+ examples = tuple(example for example in documented.spec.examples if example.public)
62
+ outcomes = {example.outcome.value for example in examples}
63
+ if outcomes != {"match", "no-match"}:
64
+ return VerificationResult(1, f"incomplete: {selector} needs public match and no-match examples")
65
+ paths = (repository / documented.source, repository / documented.test)
66
+ for path in paths:
67
+ if not path.is_file():
68
+ return VerificationResult(1, f"incomplete: expected authored file {path.relative_to(repository)}")
69
+ if "TODO" in path.read_text(encoding="utf-8"):
70
+ return VerificationResult(1, f"incomplete: resolve TODOs in {path.relative_to(repository)}")
71
+ return VerificationResult(
72
+ 0,
73
+ f"ok: {selector} is registered with complete metadata and public accept/reject examples",
74
+ )
75
+
76
+
77
+ def plan_new(root: Path, selector: RuleSelector, *, category: str, summary: str) -> AuthoringPlan:
78
+ if selector.engine is RuleEngine.TEXT:
79
+ msg = "text rules use the shared textlint catalog and are not scaffolded yet"
80
+ raise ValueError(msg)
81
+ code = _next_code(root, selector.engine)
82
+ slug = str(selector.rule_id)
83
+ snake = slug.replace("-", "_")
84
+ class_name = "".join(part.capitalize() for part in slug.split("-"))
85
+ if selector.engine is RuleEngine.ESLINT:
86
+ implementation = root / "packages/typescript/src/rules" / f"{slug}.ts"
87
+ test = root / "packages/typescript/tests/rules" / f"{slug}.test.ts"
88
+ files = (
89
+ (implementation, _eslint_implementation(slug, class_name, category, summary)),
90
+ (test, _eslint_test(slug, class_name)),
91
+ )
92
+ else:
93
+ package = selector.engine.value
94
+ module = f"sarj_{package}_lint"
95
+ implementation = root / f"packages/{package}/src/{module}/rules" / f"{snake}.py"
96
+ test = root / f"packages/{package}/tests/rules" / f"test_{snake}.py"
97
+ files = (
98
+ (
99
+ implementation,
100
+ _python_implementation(module, class_name, slug, code or "", category=category, summary=summary),
101
+ ),
102
+ (test, _python_test(module, class_name, snake)),
103
+ )
104
+ conflicts = [path for path, _ in files if path.exists()]
105
+ if conflicts:
106
+ msg = "rule scaffold target already exists: " + ", ".join(str(path.relative_to(root)) for path in conflicts)
107
+ raise FileExistsError(msg)
108
+ return AuthoringPlan(selector, code, files)
109
+
110
+
111
+ def apply(plan: AuthoringPlan, root: Path) -> None:
112
+ mutation = transaction.FileTransaction.capture(root, tuple(path for path, _ in plan.files))
113
+ try:
114
+ for path, contents in plan.files:
115
+ transaction.assert_expected(root, path, None)
116
+ transaction.atomic_write_text(root, path, contents)
117
+ mutation.mark_written(path)
118
+ except BaseException:
119
+ _ = mutation.rollback()
120
+ raise
121
+
122
+
123
+ def _registry_path(root: Path, engine: RuleEngine) -> Path:
124
+ if engine is RuleEngine.ESLINT:
125
+ return root / "packages/typescript/src/index.ts"
126
+ package = engine.value
127
+ return root / f"packages/{package}/src/sarj_{package}_lint/rules/_registry.py"
128
+
129
+
130
+ def _next_code(root: Path, engine: RuleEngine) -> str | None:
131
+ band = _BANDS.get(engine)
132
+ if band is None:
133
+ return None
134
+ used = {number for number in _code_numbers(_source_texts(root)) if number in band}
135
+ available = max(used, default=band.start - 1) + 1
136
+ if available not in band:
137
+ msg = f"no unreserved SARJ codes remain in the {engine.value} band"
138
+ raise ValueError(msg)
139
+ return f"SARJ{available:03d}"
140
+
141
+
142
+ def _source_texts(root: Path) -> Iterable[str]:
143
+ for base in (root / "packages", root / "tests"):
144
+ if base.is_dir():
145
+ for path in base.rglob("*"):
146
+ if path.is_file() and path.suffix in {".py", ".ts", ".json"}:
147
+ try:
148
+ yield path.read_text(encoding="utf-8")
149
+ except UnicodeDecodeError:
150
+ continue
151
+
152
+
153
+ def _code_numbers(texts: Iterable[str]) -> Iterable[int]:
154
+ for text in texts:
155
+ for match in _CODE.finditer(text):
156
+ yield int(match.group("number"))
157
+
158
+
159
+ def _python_implementation(module: str, class_name: str, slug: str, code: str, *, category: str, summary: str) -> str:
160
+ example_factory = module.removeprefix("sarj_").removesuffix("_lint")
161
+ return f'''"""{code} — {summary}"""
162
+
163
+ from __future__ import annotations
164
+
165
+ from pathlib import PurePosixPath
166
+ from typing import TYPE_CHECKING, final, override
167
+
168
+ from {module}.rule_base import AutofixPolicy, Diagnostic, ExampleFile, ExampleOutcome, Rule, RuleCategory, RuleDocumentation, RuleExample
169
+
170
+ if TYPE_CHECKING:
171
+ from pathlib import Path
172
+
173
+
174
+ @final
175
+ class {class_name}(Rule):
176
+ id = "{slug}"
177
+ code = "{code}"
178
+ documentation = RuleDocumentation(
179
+ summary={summary!r},
180
+ rationale="TODO: explain the concrete failure mode.",
181
+ remediation="TODO: give the smallest safe remediation.",
182
+ category=RuleCategory.{category.upper()},
183
+ autofix=AutofixPolicy.NONE,
184
+ limitations=("TODO: document the applicability boundary.",),
185
+ examples=(
186
+ RuleExample(example_id="rejects-antipattern", title="Reject the anti-pattern", outcome=ExampleOutcome.MATCH, files=(ExampleFile.{example_factory}("case.{example_factory}", "TODO invalid example\\n"),), focus_path=PurePosixPath("case.{example_factory}"), expected_count=1, public=True),
187
+ RuleExample(example_id="accepts-alternative", title="Accept the preferred alternative", outcome=ExampleOutcome.NO_MATCH, files=(ExampleFile.{example_factory}("case.{example_factory}", "TODO valid example\\n"),), focus_path=PurePosixPath("case.{example_factory}"), expected_count=0, public=True),
188
+ ),
189
+ )
190
+ description = documentation.summary
191
+
192
+ @override
193
+ def check(self, path: Path, source: str) -> list[Diagnostic]:
194
+ raise NotImplementedError("TODO: implement conservative detection")
195
+ '''
196
+
197
+
198
+ def _python_test(module: str, class_name: str, snake: str) -> str:
199
+ return f"""from {module}.rules.{snake} import {class_name}
200
+
201
+
202
+ def test_documented_examples_are_present() -> None:
203
+ outcomes = {{example.outcome.value for example in {class_name}.documentation.examples}}
204
+ assert outcomes == {{"match", "no-match"}}
205
+ """
206
+
207
+
208
+ def _eslint_implementation(slug: str, name: str, category: str, summary: str) -> str:
209
+ message = name[0].lower() + name[1:]
210
+ return f"""import {{ createRule, type RuleDocumentation }} from "./_docs.js";
211
+
212
+ type MessageIds = "{message}";
213
+ type Options = readonly [];
214
+
215
+ export const {message}Documentation = {{
216
+ summary: {summary!r},
217
+ rationale: "TODO: explain the concrete failure mode.",
218
+ remediation: "TODO: give the smallest safe remediation.",
219
+ category: "{category}",
220
+ limitations: ["TODO: document the applicability boundary."],
221
+ examples: [
222
+ {{ id: "accepts-alternative", title: "Accept the preferred alternative", outcome: "no-match", files: [{{ path: "case.ts", source: "// TODO valid example" }}], focusPath: "case.ts", expectedCount: 0, public: true }},
223
+ {{ id: "rejects-antipattern", title: "Reject the anti-pattern", outcome: "match", files: [{{ path: "case.ts", source: "// TODO invalid example" }}], focusPath: "case.ts", expectedCount: 1, public: true }},
224
+ ],
225
+ }} as const satisfies RuleDocumentation;
226
+
227
+ export default createRule<Options, MessageIds>({{
228
+ name: "{slug}",
229
+ meta: {{ type: "problem", docs: {{ description: {message}Documentation.summary }}, schema: [], messages: {{ {message}: {summary!r} }} }},
230
+ defaultOptions: [],
231
+ create() {{ throw new Error("TODO: implement conservative detection"); }},
232
+ }});
233
+ """
234
+
235
+
236
+ def _eslint_test(slug: str, name: str) -> str:
237
+ message = name[0].lower() + name[1:]
238
+ return f"""import {{ describe, expect, it }} from "vitest";
239
+ import {{ {message}Documentation }} from "../../src/rules/{slug}.js";
240
+
241
+ describe("{slug}", () => {{
242
+ it("declares public accept and reject examples", () => {{
243
+ expect({message}Documentation.examples.map((item) => item.outcome).sort()).toEqual(["match", "no-match"]);
244
+ }});
245
+ }});
246
+ """