vigiles 2.5.0 → 3.0.0
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.
- package/.claude-plugin/plugin.json +9 -0
- package/README.md +73 -434
- package/action.yml +143 -7
- package/dist/action-gate.d.ts +1 -1
- package/dist/action-gate.js +1 -1
- package/dist/adapter-conformance.d.ts +30 -0
- package/dist/adapter-conformance.js +153 -0
- package/dist/adapter-registry.d.ts +42 -0
- package/dist/adapter-registry.js +55 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +16 -0
- package/dist/adapters/claude-code/adapter.d.ts +3 -0
- package/dist/adapters/claude-code/adapter.js +46 -0
- package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
- package/dist/adapters/claude-code/dialect.d.ts +13 -0
- package/dist/adapters/claude-code/dialect.js +51 -0
- package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
- package/dist/adapters/claude-code/egress-entry.js +115 -0
- package/dist/adapters/claude-code/egress-proxy.d.ts +2 -0
- package/dist/adapters/claude-code/egress-proxy.js +60 -0
- package/dist/adapters/claude-code/egress.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -0
- package/dist/adapters/claude-code/eval-baseline.d.ts +68 -0
- package/dist/adapters/claude-code/eval-baseline.js +173 -0
- package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
- package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
- package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
- package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
- package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
- package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
- package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
- package/dist/adapters/claude-code/hook-protocol.js +10 -0
- package/dist/adapters/claude-code/layout.d.ts +8 -0
- package/dist/adapters/claude-code/layout.js +18 -0
- package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
- package/dist/adapters/claude-code/model-mock.d.ts +11 -0
- package/dist/adapters/claude-code/model-mock.js +10 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
- package/dist/adapters/claude-code/plugin-loader.js +19 -0
- package/dist/adapters/claude-code/run-hook.d.ts +204 -0
- package/dist/adapters/claude-code/run-hook.js +418 -0
- package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
- package/dist/adapters/claude-code/run-scripts.js +150 -0
- package/dist/adapters/claude-code/runtime.d.ts +16 -0
- package/dist/adapters/claude-code/runtime.js +39 -0
- package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +45 -4
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +76 -8
- package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
- package/dist/adapters/codex/adapter.d.ts +3 -0
- package/dist/adapters/codex/adapter.js +49 -0
- package/dist/adapters/codex/dialect.d.ts +10 -0
- package/dist/adapters/codex/dialect.js +30 -0
- package/dist/adapters/codex/driver.d.ts +20 -0
- package/dist/adapters/codex/driver.js +89 -0
- package/dist/adapters/codex/hook-protocol.d.ts +10 -0
- package/dist/adapters/codex/hook-protocol.js +18 -0
- package/dist/adapters/codex/layout.d.ts +16 -0
- package/dist/adapters/codex/layout.js +18 -0
- package/dist/adapters/codex/mock-model.d.ts +52 -0
- package/dist/adapters/codex/mock-model.js +210 -0
- package/dist/adapters/codex/model-mock.d.ts +11 -0
- package/dist/adapters/codex/model-mock.js +10 -0
- package/dist/adapters/codex/runtime.d.ts +38 -0
- package/dist/adapters/codex/runtime.js +62 -0
- package/dist/adapters/opencode/adapter.d.ts +3 -0
- package/dist/adapters/opencode/adapter.js +48 -0
- package/dist/adapters/opencode/dialect.d.ts +10 -0
- package/dist/adapters/opencode/dialect.js +36 -0
- package/dist/adapters/opencode/layout.d.ts +9 -0
- package/dist/adapters/opencode/layout.js +25 -0
- package/dist/adapters/opencode/model-mock.d.ts +10 -0
- package/dist/adapters/opencode/model-mock.js +10 -0
- package/dist/adapters/opencode/runtime.d.ts +9 -0
- package/dist/adapters/opencode/runtime.js +21 -0
- package/dist/claude-code.d.ts +16 -0
- package/dist/claude-code.js +32 -0
- package/dist/cli-flags.d.ts +22 -0
- package/dist/cli-flags.js +38 -0
- package/dist/cli.js +406 -206
- package/dist/codex.d.ts +20 -0
- package/dist/codex.js +36 -0
- package/dist/community-skills.d.ts +1 -1
- package/dist/community-skills.js +2 -2
- package/dist/core/adapter.d.ts +88 -0
- package/dist/core/adapter.js +3 -0
- package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
- package/dist/{compile.js → core/compile.js} +68 -53
- package/dist/core/compose.d.ts +79 -0
- package/dist/core/compose.js +145 -0
- package/dist/core/dialect.d.ts +51 -0
- package/dist/core/dialect.js +3 -0
- package/dist/core/harness-driver.d.ts +134 -0
- package/dist/core/harness-driver.js +3 -0
- package/dist/core/hook-protocol.d.ts +28 -0
- package/dist/core/hook-protocol.js +3 -0
- package/dist/core/layout.d.ts +43 -0
- package/dist/core/layout.js +3 -0
- package/dist/core/model-mock.d.ts +27 -0
- package/dist/core/model-mock.js +3 -0
- package/dist/core/refs.d.ts +62 -0
- package/dist/{refs.js → core/refs.js} +49 -23
- package/dist/core/runtime.d.ts +38 -0
- package/dist/core/runtime.js +3 -0
- package/dist/{types.d.ts → core/types.d.ts} +29 -0
- package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
- package/dist/{validate.js → core/validate.js} +41 -4
- package/dist/e2e.d.ts +19 -0
- package/dist/e2e.js +39 -0
- package/dist/harness-assert.d.ts +60 -11
- package/dist/harness-assert.js +111 -9
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +32 -0
- package/dist/leaderboard.d.ts +33 -0
- package/dist/leaderboard.js +107 -0
- package/dist/linting.d.ts +9 -0
- package/dist/linting.js +25 -0
- package/dist/plugin-loader.d.ts +6 -3
- package/dist/plugin-loader.js +101 -49
- package/dist/scan.d.ts +49 -0
- package/dist/scan.js +177 -0
- package/dist/setup-plan.d.ts +51 -0
- package/dist/setup-plan.js +85 -0
- package/dist/skill-test.d.ts +1 -1
- package/dist/skill-test.js +1 -1
- package/dist/test-coverage.d.ts +71 -0
- package/dist/test-coverage.js +228 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.js +28 -0
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +35 -16
- package/skills/audit-feedback-loop/SKILL.md +76 -0
- package/skills/edit-spec/SKILL.md +131 -0
- package/skills/enforce-rules-format/SKILL.md +71 -0
- package/skills/generate-logo/SKILL.md +103 -0
- package/skills/generate-rule/SKILL.md +64 -0
- package/skills/linter-docs/clippy.md +241 -0
- package/skills/linter-docs/eslint.md +384 -0
- package/skills/linter-docs/pylint.md +288 -0
- package/skills/linter-docs/rubocop.md +277 -0
- package/skills/linter-docs/ruff.md +187 -0
- package/skills/linter-docs/stylelint.md +247 -0
- package/skills/migrate-to-spec/SKILL.md +126 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/skills/test-harness/SKILL.md +157 -0
- package/dist/action.d.ts +0 -7
- package/dist/action.js +0 -180
- package/dist/refs.d.ts +0 -44
- package/dist/run-hook.d.ts +0 -77
- package/dist/run-hook.js +0 -80
- package/dist/run-scripts.d.ts +0 -20
- package/dist/run-scripts.js +0 -70
- /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
- /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
- /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
- /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
- /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
- /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
- /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
- /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
- /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
- /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
- /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
- /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
- /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
- /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
- /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
- /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
- /package/dist/{coverage.js → core/coverage.js} +0 -0
- /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
- /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
- /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
- /package/dist/{evolve.js → core/evolve.js} +0 -0
- /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
- /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
- /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
- /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
- /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
- /package/dist/{generate-types.js → core/generate-types.js} +0 -0
- /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
- /package/dist/{hash.js → core/hash.js} +0 -0
- /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
- /package/dist/{inline.js → core/inline.js} +0 -0
- /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
- /package/dist/{integrity.js → core/integrity.js} +0 -0
- /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
- /package/dist/{linters.js → core/linters.js} +0 -0
- /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
- /package/dist/{mcp.js → core/mcp.js} +0 -0
- /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
- /package/dist/{orphans.js → core/orphans.js} +0 -0
- /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
- /package/dist/{proofs.js → core/proofs.js} +0 -0
- /package/dist/{session.d.ts → core/session.d.ts} +0 -0
- /package/dist/{session.js → core/session.js} +0 -0
- /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
- /package/dist/{sidecar.js → core/sidecar.js} +0 -0
- /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
- /package/dist/{spec.js → core/spec.js} +0 -0
- /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
- /package/dist/{symbols.js → core/symbols.js} +0 -0
- /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
- /package/dist/{test-utils.js → core/test-utils.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Pylint — Reference
|
|
2
|
+
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and `pr-to-lint-rule` (write custom checkers).
|
|
4
|
+
|
|
5
|
+
## Check Existing Plugins First
|
|
6
|
+
|
|
7
|
+
Before writing a custom checker, search these plugins — the pattern may already be covered:
|
|
8
|
+
|
|
9
|
+
| Plugin | Scope | Key messages to know |
|
|
10
|
+
| ------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `pylint` (core) | Convention, Refactor, Warning, Error, Fatal | `C0114` (missing-module-docstring), `R0902` (too-many-instance-attributes), `W0612` (unused-variable), `E1101` (no-member), `W0611` (unused-import) |
|
|
12
|
+
| `pylint-django` | Django conventions | `E5110` (no-member on Django models), `W5101` (fixme in templates), `C5105` (string-used-as-django-setting) |
|
|
13
|
+
| `pylint-celery` | Celery task patterns | Detects common Celery anti-patterns (task retry without max_retries, etc.) |
|
|
14
|
+
| `pylint-pydantic` | Pydantic model checking | Suppresses false positives from Pydantic's metaclass magic |
|
|
15
|
+
| `pylint-pytest` | pytest conventions | `W6301` (useless-pytest-mark-decorator), `W6302` (unnecessary-pytest-mark-parametrize) |
|
|
16
|
+
| `pylint-flask` | Flask patterns | Suppresses false positives for Flask's app context, request globals |
|
|
17
|
+
| `pylint-sqlalchemy` | SQLAlchemy ORM | Suppresses false `no-member` on SQLAlchemy models |
|
|
18
|
+
| `pylint-protobuf` | Protobuf message checking | Type-checks protobuf field access, catches typos in field names |
|
|
19
|
+
| `pylint-import-modules` | Import style enforcement | `C6201` (import-modules-only) — enforce `import module` over `from module import name` |
|
|
20
|
+
| `pylint-secure-coding-standard` | Security patterns | Detects insecure patterns: `eval`, `exec`, hardcoded passwords, insecure hash algorithms |
|
|
21
|
+
|
|
22
|
+
**Tip:** Many patterns can be handled by Pylint's built-in `bad-names`, `good-names`, `bad-functions`, or the `--disable`/`--enable` system without a custom checker.
|
|
23
|
+
|
|
24
|
+
**Also consider Ruff.** If the project uses Ruff, it reimplements many Pylint rules (prefixed `PL`) at much higher speed. Check Ruff's rule catalog before writing a Pylint-specific checker.
|
|
25
|
+
|
|
26
|
+
## Checker Anatomy
|
|
27
|
+
|
|
28
|
+
Every Pylint checker inherits from `BaseChecker`:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
"""Checker for direct database queries outside the repository layer."""
|
|
32
|
+
|
|
33
|
+
from astroid import nodes
|
|
34
|
+
from pylint.checkers import BaseChecker
|
|
35
|
+
from pylint.interfaces import HIGH
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NoDirectDbQueryChecker(BaseChecker):
|
|
39
|
+
name = "no-direct-db-query"
|
|
40
|
+
|
|
41
|
+
msgs = {
|
|
42
|
+
"C9001": (
|
|
43
|
+
"Use %sRepository instead of %s.%s",
|
|
44
|
+
"no-direct-db-query",
|
|
45
|
+
"Direct model queries should go through the repository layer.",
|
|
46
|
+
),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Optional: add configuration options
|
|
50
|
+
options = (
|
|
51
|
+
(
|
|
52
|
+
"allowed-models",
|
|
53
|
+
{
|
|
54
|
+
"default": (),
|
|
55
|
+
"type": "csv",
|
|
56
|
+
"metavar": "<models>",
|
|
57
|
+
"help": "Models that are allowed to be queried directly.",
|
|
58
|
+
},
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def visit_call(self, node: nodes.Call) -> None:
|
|
63
|
+
if not isinstance(node.func, nodes.Attribute):
|
|
64
|
+
return
|
|
65
|
+
if not isinstance(node.func.expr, nodes.Name):
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
model = node.func.expr.name
|
|
69
|
+
method = node.func.attrname
|
|
70
|
+
|
|
71
|
+
if method in ("objects", "filter", "get", "all", "count", "values"):
|
|
72
|
+
if model not in self.linter.config.allowed_models:
|
|
73
|
+
self.add_message(
|
|
74
|
+
"no-direct-db-query",
|
|
75
|
+
node=node,
|
|
76
|
+
args=(model, model, method),
|
|
77
|
+
confidence=HIGH,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def register(linter):
|
|
82
|
+
linter.register_checker(NoDirectDbQueryChecker(linter))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Key concepts
|
|
86
|
+
|
|
87
|
+
| Concept | Purpose | Notes |
|
|
88
|
+
| ------------- | ------------------- | ---------------------------------------------------------- |
|
|
89
|
+
| `name` | Checker identifier | Must be unique across all checkers |
|
|
90
|
+
| `msgs` | Message dictionary | Key format: `{C,R,W,E,F}{4 digits}` — category + unique ID |
|
|
91
|
+
| `options` | Configuration | Exposed via `pylintrc` or command line |
|
|
92
|
+
| `visit_*` | AST visitor methods | Called for each matching node type |
|
|
93
|
+
| `leave_*` | Post-visit methods | Called when leaving a node (post-order) |
|
|
94
|
+
| `add_message` | Report a violation | Pass message ID, node, args for formatting |
|
|
95
|
+
| `register` | Plugin entry point | Module-level function that registers the checker |
|
|
96
|
+
|
|
97
|
+
### Message categories
|
|
98
|
+
|
|
99
|
+
| Prefix | Category | When to use |
|
|
100
|
+
| ------ | ---------- | ------------------------------------------ |
|
|
101
|
+
| `C` | Convention | Style/naming conventions |
|
|
102
|
+
| `R` | Refactor | Code that works but should be restructured |
|
|
103
|
+
| `W` | Warning | Probable bugs or risky patterns |
|
|
104
|
+
| `E` | Error | Definite bugs or broken code |
|
|
105
|
+
| `F` | Fatal | Pylint can't process the file |
|
|
106
|
+
|
|
107
|
+
Choose a unique 4-digit code starting from `9001` to avoid collisions with core Pylint. Check existing codes with `pylint --list-msgs`.
|
|
108
|
+
|
|
109
|
+
## AST Nodes — Cheat Sheet (astroid)
|
|
110
|
+
|
|
111
|
+
Pylint uses `astroid` for AST parsing, which adds type inference on top of Python's `ast` module:
|
|
112
|
+
|
|
113
|
+
| You want to detect | Node type / visitor | Notes |
|
|
114
|
+
| -------------------------- | --------------------------------------------------- | --------------------------------------------- |
|
|
115
|
+
| Function call `foo()` | `visit_call` + check `node.func` | `node.func` is a `Name` or `Attribute` |
|
|
116
|
+
| Method call `obj.method()` | `visit_call` + `isinstance(node.func, Attribute)` | `node.func.attrname` = method name |
|
|
117
|
+
| Import `import X` | `visit_import` | `node.names` = list of `(name, alias)` tuples |
|
|
118
|
+
| Import `from X import Y` | `visit_importfrom` | `node.modname` = module, `node.names` = names |
|
|
119
|
+
| Class definition | `visit_classdef` | `node.name`, `node.bases`, `node.body` |
|
|
120
|
+
| Function definition | `visit_functiondef` | `node.name`, `node.args`, `node.body` |
|
|
121
|
+
| Assignment `x = ...` | `visit_assign` | `node.targets`, `node.value` |
|
|
122
|
+
| String literal | `visit_const` + check `isinstance(node.value, str)` | |
|
|
123
|
+
| Decorator `@foo` | `visit_decorators` | Or check `node.decorators` on function/class |
|
|
124
|
+
| `raise` statement | `visit_raise` | `node.exc` = exception expression |
|
|
125
|
+
| `with` statement | `visit_with` | `node.items` = context manager list |
|
|
126
|
+
| `try`/`except` | `visit_tryexcept` | `node.handlers` = list of except clauses |
|
|
127
|
+
|
|
128
|
+
### astroid type inference
|
|
129
|
+
|
|
130
|
+
astroid can infer types, which is more powerful than plain AST:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
def visit_call(self, node: nodes.Call) -> None:
|
|
134
|
+
# Infer what the function resolves to
|
|
135
|
+
try:
|
|
136
|
+
for inferred in node.func.infer():
|
|
137
|
+
if isinstance(inferred, nodes.FunctionDef):
|
|
138
|
+
# We know the actual function being called
|
|
139
|
+
if inferred.name == "dangerous_function":
|
|
140
|
+
self.add_message("no-dangerous-call", node=node)
|
|
141
|
+
except astroid.InferenceError:
|
|
142
|
+
pass # Can't determine — skip
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Caution:** `infer()` can raise `InferenceError` and can return `Uninferable`. Always handle both.
|
|
146
|
+
|
|
147
|
+
## Testing Checkers
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
"""Tests for no_direct_db_query checker."""
|
|
151
|
+
|
|
152
|
+
import astroid
|
|
153
|
+
import pylint.testutils
|
|
154
|
+
|
|
155
|
+
from my_checkers.no_direct_db_query import NoDirectDbQueryChecker
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestNoDirectDbQueryChecker(pylint.testutils.UnittestLinter):
|
|
159
|
+
CHECKER_CLASS = NoDirectDbQueryChecker
|
|
160
|
+
|
|
161
|
+
def test_direct_query_flagged(self):
|
|
162
|
+
node = astroid.extract_node("""
|
|
163
|
+
User.objects.filter(active=True) #@
|
|
164
|
+
""")
|
|
165
|
+
with self.assertAddsMessages(
|
|
166
|
+
pylint.testutils.MessageTest(
|
|
167
|
+
msg_id="no-direct-db-query",
|
|
168
|
+
node=node,
|
|
169
|
+
args=("User", "User", "objects"),
|
|
170
|
+
),
|
|
171
|
+
):
|
|
172
|
+
self.checker.visit_call(node)
|
|
173
|
+
|
|
174
|
+
def test_repository_call_allowed(self):
|
|
175
|
+
node = astroid.extract_node("""
|
|
176
|
+
UserRepository.active_users() #@
|
|
177
|
+
""")
|
|
178
|
+
with self.assertNoMessages():
|
|
179
|
+
self.checker.visit_call(node)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Testing best practices:**
|
|
183
|
+
|
|
184
|
+
1. **Use `astroid.extract_node` with `#@` marker.** The marker indicates which node to extract from the code.
|
|
185
|
+
2. **Test with `assertAddsMessages` and `assertNoMessages`.** These are the canonical assertion methods.
|
|
186
|
+
3. **Test configuration options.** If your checker has options, test each combination.
|
|
187
|
+
4. **Test inference edge cases.** Code with dynamic attributes, metaclasses, or `__getattr__` may cause `InferenceError`.
|
|
188
|
+
5. **Run with `pytest` not `unittest`.** Pylint's test utils work with both, but pytest gives better output.
|
|
189
|
+
|
|
190
|
+
## Registration
|
|
191
|
+
|
|
192
|
+
### As a Pylint plugin
|
|
193
|
+
|
|
194
|
+
Add to `pyproject.toml`:
|
|
195
|
+
|
|
196
|
+
```toml
|
|
197
|
+
[tool.pylint.main]
|
|
198
|
+
load-plugins = ["my_checkers.no_direct_db_query"]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Or `.pylintrc`:
|
|
202
|
+
|
|
203
|
+
```ini
|
|
204
|
+
[MAIN]
|
|
205
|
+
load-plugins=my_checkers.no_direct_db_query
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The module must be importable from `PYTHONPATH`. For a local checker, put it in the project and ensure the path is set.
|
|
209
|
+
|
|
210
|
+
### vigiles enforce() reference
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
enforce(
|
|
214
|
+
"pylint/no-direct-db-query",
|
|
215
|
+
"Use repository pattern for all DB queries.",
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
vigiles checks `pylint --help-msg=no-direct-db-query` and verifies the message exists and is enabled.
|
|
220
|
+
|
|
221
|
+
## Edge Cases and Gotchas
|
|
222
|
+
|
|
223
|
+
### Symbolic names vs message codes
|
|
224
|
+
|
|
225
|
+
Pylint messages have both a code (`C0114`) and a symbolic name (`missing-module-docstring`). Both work in vigiles:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
enforce("pylint/C0114", "All modules need docstrings.");
|
|
229
|
+
enforce("pylint/missing-module-docstring", "All modules need docstrings.");
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Prefer symbolic names — they're readable and stable across Pylint versions.
|
|
233
|
+
|
|
234
|
+
### astroid inference limitations
|
|
235
|
+
|
|
236
|
+
astroid can't infer:
|
|
237
|
+
|
|
238
|
+
- Dynamically constructed classes (`type("Foo", (Base,), {...})`)
|
|
239
|
+
- Heavy metaprogramming (`__init_subclass__`, custom metaclasses)
|
|
240
|
+
- C extensions (some methods on `numpy`, `pandas` are opaque)
|
|
241
|
+
- Runtime-only attributes (`setattr`, `__dict__` manipulation)
|
|
242
|
+
|
|
243
|
+
If your convention targets heavily dynamic code, expect false negatives. Add a comment explaining the limitation in the checker.
|
|
244
|
+
|
|
245
|
+
### Plugin vs Ruff
|
|
246
|
+
|
|
247
|
+
For simple pattern bans, Ruff's built-in rules or `flake8-*` plugins reimplemented in Ruff are 10-100x faster. Use a Pylint custom checker only when you need:
|
|
248
|
+
|
|
249
|
+
- Type inference (astroid's `infer()`)
|
|
250
|
+
- Cross-function or cross-module analysis
|
|
251
|
+
- Configuration options exposed in `pylintrc`
|
|
252
|
+
- Integration with Django/Flask/SQLAlchemy brain plugins
|
|
253
|
+
|
|
254
|
+
### Message ID collisions
|
|
255
|
+
|
|
256
|
+
Core Pylint uses codes `C0001`-`C8999`, `W0001`-`W8999`, etc. Custom checkers should use `9000+` to avoid collisions. Check with:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
pylint --list-msgs | grep "C90\|W90\|E90"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Performance
|
|
263
|
+
|
|
264
|
+
- Pylint is slow on large codebases. Custom checkers add overhead per-file.
|
|
265
|
+
- Avoid `infer()` in hot paths — each call can trigger recursive type resolution.
|
|
266
|
+
- Use `visit_module` + early return to skip files that can't contain violations (e.g., skip test files for production-only rules).
|
|
267
|
+
- Consider `--jobs=N` for parallel execution, but note that custom checkers must be pickle-safe for multiprocessing.
|
|
268
|
+
|
|
269
|
+
### Monorepo considerations
|
|
270
|
+
|
|
271
|
+
- Pylint resolves `pylintrc` / `pyproject.toml` from `cwd`, not from the file being linted. In a monorepo, run Pylint per-package.
|
|
272
|
+
- vigiles checks `pylint --help-msg=<name>` from the project `basePath`. If packages have different plugins loaded, run from each package root.
|
|
273
|
+
- Custom checkers must be on `PYTHONPATH`. In a monorepo, use relative `load-plugins` paths or install checkers as a development package.
|
|
274
|
+
|
|
275
|
+
## Mapping PR Feedback to Checker Strategy
|
|
276
|
+
|
|
277
|
+
| PR comment pattern | Best approach |
|
|
278
|
+
| ------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
279
|
+
| "Don't import X directly" | `pylint/no-name-in-module` or custom checker with `visit_importfrom` |
|
|
280
|
+
| "Don't call X()" | `pylint/bad-functions` config or custom checker with `visit_call` |
|
|
281
|
+
| "Missing docstring" | `pylint/missing-function-docstring` (C0116) — already exists |
|
|
282
|
+
| "Function too long" | `pylint/too-many-statements` (R0915) — configure threshold |
|
|
283
|
+
| "Too many arguments" | `pylint/too-many-arguments` (R0913) — configure `max-args` |
|
|
284
|
+
| "Use logging instead of print" | Ruff `T201` or custom checker banning `print()` |
|
|
285
|
+
| "Don't use global state" | Custom checker detecting `global` keyword or module-level mutation |
|
|
286
|
+
| "Always type-hint public methods" | `pylint/missing-function-docstring` doesn't cover this — use `mypy --strict` or custom checker |
|
|
287
|
+
| "Don't catch bare Exception" | `pylint/broad-exception-caught` (W0718) — already exists |
|
|
288
|
+
| "Don't use mutable default arguments" | `pylint/dangerous-default-value` (W0102) — already exists |
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# RuboCop — Reference
|
|
2
|
+
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and `pr-to-lint-rule` (write custom cops).
|
|
4
|
+
|
|
5
|
+
## Check Existing Gems First
|
|
6
|
+
|
|
7
|
+
Before writing a custom cop, search these gems — the pattern may already be covered:
|
|
8
|
+
|
|
9
|
+
| Gem | Scope | Key cops to know |
|
|
10
|
+
| --------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `rubocop` (core) | Style, Lint, Metrics, Naming, Security | `Style/FrozenStringLiteralComment`, `Lint/UnusedMethodArgument`, `Metrics/MethodLength`, `Naming/MethodName`, `Security/Eval` |
|
|
12
|
+
| `rubocop-rails` | Rails conventions | `Rails/HttpPositionalArguments`, `Rails/SkipsModelValidations`, `Rails/Output`, `Rails/HasAndBelongsToMany`, `Rails/DynamicFindBy` |
|
|
13
|
+
| `rubocop-rspec` | RSpec test patterns | `RSpec/ExampleLength`, `RSpec/MultipleExpectations`, `RSpec/NestedGroups`, `RSpec/LetSetup`, `RSpec/MessageSpies` |
|
|
14
|
+
| `rubocop-minitest` | Minitest patterns | `Minitest/AssertEmptyLiteral`, `Minitest/RefuteNil`, `Minitest/AssertInDelta` |
|
|
15
|
+
| `rubocop-performance` | Performance anti-patterns | `Performance/StringReplacement`, `Performance/Detect`, `Performance/Count`, `Performance/FlatMap`, `Performance/CaseWhenSplat` |
|
|
16
|
+
| `rubocop-rake` | Rakefile patterns | `Rake/Desc`, `Rake/DuplicateTask`, `Rake/MethodDefinitionInTask` |
|
|
17
|
+
| `rubocop-sorbet` | Sorbet type checking | `Sorbet/ForbidSuperclassConstLiteral`, `Sorbet/ValidSigil`, `Sorbet/SignatureBuildOrder` |
|
|
18
|
+
| `rubocop-graphql` | GraphQL conventions | `GraphQL/FieldDescription`, `GraphQL/ObjectDescription`, `GraphQL/ArgumentDescription` |
|
|
19
|
+
| `rubocop-factory_bot` | FactoryBot patterns | `FactoryBot/ConsistentParenthesesStyle`, `FactoryBot/CreateList`, `FactoryBot/SyntaxMethods` |
|
|
20
|
+
| `rubocop-capybara` | Capybara test patterns | `Capybara/CurrentPathExpectation`, `Capybara/VisibilityMatcher`, `Capybara/SpecificMatcher` |
|
|
21
|
+
|
|
22
|
+
**Tip:** Many patterns can be handled by configuring existing cops rather than writing new ones. Check if an existing cop has configurable `AllowedMethods`, `AllowedPatterns`, or `Include`/`Exclude` options first.
|
|
23
|
+
|
|
24
|
+
## Cop Anatomy
|
|
25
|
+
|
|
26
|
+
Every RuboCop cop inherits from `RuboCop::Cop::Base`:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# frozen_string_literal: true
|
|
30
|
+
|
|
31
|
+
module RuboCop
|
|
32
|
+
module Cop
|
|
33
|
+
module Custom
|
|
34
|
+
# Disallow direct database queries outside the repository layer.
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# # bad
|
|
38
|
+
# User.where(active: true)
|
|
39
|
+
#
|
|
40
|
+
# # good
|
|
41
|
+
# UserRepository.active_users
|
|
42
|
+
class NoDirectDbQuery < Base
|
|
43
|
+
MSG = 'Use the repository pattern — call `%<model>sRepository` instead of `%<model>s.%<method>s`.'
|
|
44
|
+
|
|
45
|
+
RESTRICT_ON_SEND = %i[where find find_by first last all count pluck].freeze
|
|
46
|
+
|
|
47
|
+
# @!method ar_model_query?(node)
|
|
48
|
+
def_node_matcher :ar_model_query?, <<~PATTERN
|
|
49
|
+
(send (const nil? _) {:where :find :find_by :first :last :all :count :pluck} ...)
|
|
50
|
+
PATTERN
|
|
51
|
+
|
|
52
|
+
def on_send(node)
|
|
53
|
+
return unless ar_model_query?(node)
|
|
54
|
+
|
|
55
|
+
model = node.receiver.short_name
|
|
56
|
+
add_offense(node, message: format(MSG, model: model, method: node.method_name))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Key concepts
|
|
65
|
+
|
|
66
|
+
| Concept | Purpose | Notes |
|
|
67
|
+
| ------------------ | --------------------------------------------- | ------------------------------------------------------------------- |
|
|
68
|
+
| `MSG` | Error message string | Use `format` with `%<name>s` for interpolation |
|
|
69
|
+
| `RESTRICT_ON_SEND` | Whitelist of method names | Performance optimization — cop only runs on matching `send` nodes |
|
|
70
|
+
| `def_node_matcher` | AST pattern matcher | DSL for matching Ruby AST patterns — replaces manual node traversal |
|
|
71
|
+
| `def_node_search` | Like `def_node_matcher` but finds all matches | Returns an enumerator of matching nodes |
|
|
72
|
+
| `add_offense` | Report a violation | Accepts `node`, optional `message:` and `severity:` |
|
|
73
|
+
|
|
74
|
+
### Cop departments
|
|
75
|
+
|
|
76
|
+
| Department | When to use |
|
|
77
|
+
| ---------- | ----------------------------------------------------- |
|
|
78
|
+
| `Lint` | Code that is/will be broken |
|
|
79
|
+
| `Style` | Code that works but violates a convention |
|
|
80
|
+
| `Metrics` | Complexity thresholds (method length, ABC size, etc.) |
|
|
81
|
+
| `Naming` | Naming conventions |
|
|
82
|
+
| `Security` | Security anti-patterns |
|
|
83
|
+
| `Custom` | Your project-specific cops |
|
|
84
|
+
|
|
85
|
+
## Node Pattern DSL — Cheat Sheet
|
|
86
|
+
|
|
87
|
+
RuboCop's node pattern DSL is the primary way to match AST nodes:
|
|
88
|
+
|
|
89
|
+
| You want to detect | Pattern | Notes |
|
|
90
|
+
| --------------------- | ----------------------------------- | --------------------------------------------- |
|
|
91
|
+
| Method call `foo` | `(send nil? :foo ...)` | `nil?` = no receiver |
|
|
92
|
+
| Method call `obj.foo` | `(send _ :foo ...)` | `_` = any receiver |
|
|
93
|
+
| Method call `Foo.bar` | `(send (const nil? :Foo) :bar ...)` | Constant receiver |
|
|
94
|
+
| String literal `"x"` | `(str "x")` | |
|
|
95
|
+
| Symbol `:x` | `(sym :x)` | |
|
|
96
|
+
| Block `foo { }` | `(block (send nil? :foo) ...)` | |
|
|
97
|
+
| Class definition | `(class (const nil? :Name) ...)` | |
|
|
98
|
+
| `if` conditional | `(if _ _ _)` | Three children: condition, if-body, else-body |
|
|
99
|
+
| Instance variable | `(ivar :@name)` | |
|
|
100
|
+
| Constant assignment | `(casgn nil? :NAME _)` | |
|
|
101
|
+
| `require "x"` | `(send nil? :require (str "x"))` | |
|
|
102
|
+
|
|
103
|
+
**Wildcards and captures:**
|
|
104
|
+
|
|
105
|
+
- `_` — matches any single node
|
|
106
|
+
- `...` — matches any number of nodes (rest)
|
|
107
|
+
- `$_` — capture a node into a variable
|
|
108
|
+
- `nil?` — matches nil (no receiver for top-level calls)
|
|
109
|
+
- `{:foo :bar}` — matches either `:foo` or `:bar`
|
|
110
|
+
- `(send _ {:puts :print :p} ...)` — matches puts/print/p on any receiver
|
|
111
|
+
|
|
112
|
+
**Pro tip:** Run `ruby-parse -e 'your_code_here'` to see the AST for any Ruby expression. The `parser` gem must be installed.
|
|
113
|
+
|
|
114
|
+
## Auto-Correct
|
|
115
|
+
|
|
116
|
+
### `extend AutoCorrector` — opt-in auto-fix
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class NoDirectDbQuery < Base
|
|
120
|
+
extend AutoCorrector
|
|
121
|
+
|
|
122
|
+
def on_send(node)
|
|
123
|
+
return unless ar_model_query?(node)
|
|
124
|
+
|
|
125
|
+
add_offense(node) do |corrector|
|
|
126
|
+
model = node.receiver.short_name
|
|
127
|
+
corrector.replace(node, "#{model}Repository.#{node.method_name}")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Available corrector methods:
|
|
134
|
+
|
|
135
|
+
| Method | What it does |
|
|
136
|
+
| ------------------------------------- | -------------------------------- |
|
|
137
|
+
| `corrector.replace(node, text)` | Replace node with text |
|
|
138
|
+
| `corrector.insert_before(node, text)` | Insert text before node |
|
|
139
|
+
| `corrector.insert_after(node, text)` | Insert text after node |
|
|
140
|
+
| `corrector.remove(node)` | Remove node |
|
|
141
|
+
| `corrector.wrap(node, before, after)` | Wrap node with before/after text |
|
|
142
|
+
|
|
143
|
+
Safety rules for auto-correct:
|
|
144
|
+
|
|
145
|
+
- **Mark unsafe corrections** with `def autocorrect_enabled? = false` or use the cop's `Safe` / `SafeAutoCorrect` metadata in `.rubocop.yml`
|
|
146
|
+
- **Never change runtime behavior.** If the fix might break code, don't auto-correct.
|
|
147
|
+
- RuboCop runs `rubocop -a` (safe only) vs `rubocop -A` (all including unsafe). Default to safe.
|
|
148
|
+
|
|
149
|
+
## Testing Cops
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
# frozen_string_literal: true
|
|
153
|
+
|
|
154
|
+
require "rubocop"
|
|
155
|
+
require "rubocop/rspec/expect_offense"
|
|
156
|
+
|
|
157
|
+
RSpec.describe RuboCop::Cop::Custom::NoDirectDbQuery, :config do
|
|
158
|
+
# Tests that valid code produces no offenses
|
|
159
|
+
it "accepts repository pattern calls" do
|
|
160
|
+
expect_no_offenses(<<~RUBY)
|
|
161
|
+
UserRepository.active_users
|
|
162
|
+
RUBY
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Tests that invalid code produces the right offense
|
|
166
|
+
it "registers offense for direct AR query" do
|
|
167
|
+
expect_offense(<<~RUBY)
|
|
168
|
+
User.where(active: true)
|
|
169
|
+
^^^^^^^^^^^^^^^^^^^^^^^^ Use the repository pattern — call `UserRepository` instead of `User.where`.
|
|
170
|
+
RUBY
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Tests auto-correction
|
|
174
|
+
it "auto-corrects to repository call" do
|
|
175
|
+
expect_offense(<<~RUBY)
|
|
176
|
+
User.find(1)
|
|
177
|
+
^^^^^^^^^^^^ Use the repository pattern — call `UserRepository` instead of `User.find`.
|
|
178
|
+
RUBY
|
|
179
|
+
|
|
180
|
+
expect_correction(<<~RUBY)
|
|
181
|
+
UserRepository.find(1)
|
|
182
|
+
RUBY
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Testing best practices:**
|
|
188
|
+
|
|
189
|
+
1. **Use `expect_offense` with caret markers.** The carets (`^`) must align with the exact offense location.
|
|
190
|
+
2. **Test edge cases.** Dynamic receivers, safe navigation (`&.`), chained methods, blocks.
|
|
191
|
+
3. **Test configuration.** If your cop respects `AllowedMethods`, test with and without the config.
|
|
192
|
+
4. **Use `:config` shared context** to get a default configuration object.
|
|
193
|
+
|
|
194
|
+
## Registration
|
|
195
|
+
|
|
196
|
+
### In `.rubocop.yml`
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
require:
|
|
200
|
+
- ./lib/rubocop/cop/custom/no_direct_db_query
|
|
201
|
+
|
|
202
|
+
Custom/NoDirectDbQuery:
|
|
203
|
+
Enabled: true
|
|
204
|
+
Include:
|
|
205
|
+
- "app/**/*.rb"
|
|
206
|
+
Exclude:
|
|
207
|
+
- "app/repositories/**/*.rb"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### vigiles enforce() reference
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
enforce(
|
|
214
|
+
"rubocop/Custom/NoDirectDbQuery",
|
|
215
|
+
"Use repository pattern for all DB queries.",
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
vigiles checks `rubocop --show-cops Custom/NoDirectDbQuery` and verifies `Enabled: true`.
|
|
220
|
+
|
|
221
|
+
## Edge Cases and Gotchas
|
|
222
|
+
|
|
223
|
+
### Safe vs unsafe cops
|
|
224
|
+
|
|
225
|
+
RuboCop distinguishes `Safe: true` (default) from `Safe: false`. Unsafe cops can produce false positives. When writing a custom cop that can't guarantee correctness (e.g., it doesn't understand metaprogramming), set:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
Custom/MyCop:
|
|
229
|
+
Safe: false
|
|
230
|
+
SafeAutoCorrect: false
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Metaprogramming blind spots
|
|
234
|
+
|
|
235
|
+
RuboCop's AST parser doesn't understand:
|
|
236
|
+
|
|
237
|
+
- `method_missing` / `respond_to_missing?`
|
|
238
|
+
- `define_method` with dynamic names
|
|
239
|
+
- `send` / `public_send` with variable method names
|
|
240
|
+
- ActiveRecord dynamic finders (`find_by_name` generated at runtime)
|
|
241
|
+
|
|
242
|
+
If your convention targets code that's often generated via metaprogramming, expect false negatives.
|
|
243
|
+
|
|
244
|
+
### Node matcher vs manual traversal
|
|
245
|
+
|
|
246
|
+
Use `def_node_matcher` for simple patterns. Switch to manual `node.children`, `node.each_descendant`, etc. when you need:
|
|
247
|
+
|
|
248
|
+
- Cross-method analysis (e.g., "if method A exists, method B must also exist")
|
|
249
|
+
- State tracking across the file (e.g., counting total occurrences)
|
|
250
|
+
- Complex conditional logic that the pattern DSL can't express
|
|
251
|
+
|
|
252
|
+
### Performance
|
|
253
|
+
|
|
254
|
+
- Always use `RESTRICT_ON_SEND` when targeting method calls — it skips the cop entirely for non-matching methods.
|
|
255
|
+
- Avoid `on_send` without `RESTRICT_ON_SEND` on large codebases — it fires for every method call.
|
|
256
|
+
- `def_node_search` iterates descendants and can be slow on deeply nested ASTs. Prefer `def_node_matcher` on specific node types.
|
|
257
|
+
|
|
258
|
+
### Monorepo considerations
|
|
259
|
+
|
|
260
|
+
- RuboCop resolves `.rubocop.yml` from the file being linted, walking up directories. Each sub-project can have its own config.
|
|
261
|
+
- vigiles checks `rubocop --show-cops <CopName>` from the project `basePath`. In a monorepo, run from each gem/app root.
|
|
262
|
+
- Custom cops must be `require`-able from the config file's location. Use relative paths or bundle them in a gem.
|
|
263
|
+
|
|
264
|
+
## Mapping PR Feedback to Cop Strategy
|
|
265
|
+
|
|
266
|
+
| PR comment pattern | Best approach |
|
|
267
|
+
| ------------------------------------------ | -------------------------------------------------------------------------------- |
|
|
268
|
+
| "Don't call X directly" | Check if an existing cop covers it; otherwise custom cop with `RESTRICT_ON_SEND` |
|
|
269
|
+
| "Use our wrapper for Y" | Custom cop — detect raw calls, suggest wrapper |
|
|
270
|
+
| "Method too long" | `Metrics/MethodLength` — configure `Max:` threshold |
|
|
271
|
+
| "Missing frozen_string_literal" | `Style/FrozenStringLiteralComment` — already exists |
|
|
272
|
+
| "Don't use `puts` in production" | Custom cop or `Rails/Output` if using Rails |
|
|
273
|
+
| "Always add description to GraphQL fields" | `rubocop-graphql` `GraphQL/FieldDescription` |
|
|
274
|
+
| "Test files too complex" | `RSpec/ExampleLength`, `RSpec/NestedGroups` — configure thresholds |
|
|
275
|
+
| "Naming convention violated" | `Naming/*` cops — highly configurable |
|
|
276
|
+
| "Security issue: eval" | `Security/Eval` — already exists |
|
|
277
|
+
| "Don't skip validations" | `Rails/SkipsModelValidations` — already exists |
|