Eigene Regeln schreiben
Die ausgelieferten Regeln sind nicht die Obergrenze. Eine Konvention, die Sie in Reviews ständig wiederholen, ist eine Regel, die Sie noch nicht geschrieben haben. Schreiben Sie sie einmal, und sie prüft von nun an jede Datei.
Eine Regel ist eine Frage, die die Engine beantwortet
Eine eigene Regel sind Daten, die die Binärdatei ausführt – eine .json-Datei in .commandments/custom/, in diesem Ordner oder einem beliebigen Unterordner. Ein Projekt mit Regeln für mehrere Teile seines Codes gruppiert sie nach dem, was sie schützen (custom/core/, custom/viewer/). Sie stellt dieselbe Engine wie jede ausgelieferte Regel. Eine Regel wird durch ihre DATEI benannt, wo immer sie liegt. Wenn man sie also in einen Ordner verschiebt, bleiben alle Erwähnungen in der Konfiguration bestehen, und zwei Dateien mit demselben Namen werden beide abgelehnt, anstatt dass eine stillschweigend gewinnt. Sie benennt die Engine, die sie prüft, die Sünde, die sie findet, und eine Abfrage: einen Selektor, der sie öffnet, dann where-Schritte, die einen Knoten behalten, und reject-Schritte, die einen verwerfen. Jeder Schritt führt genau eine Prüfung durch; eine Regel ist nur so gut wie die Frage, die jeder Schritt stellt.
{
"engine": "php",
"sin": {
"name": "raw-sql",
"description": "SQL, das inline an einer Aufrufstelle geschrieben wurde.",
"rule": "Abfragen gehen durch das Repository, das die Tabelle besitzt.",
"skill": "no-raw-sql"
},
"find": {
"select": "call",
"where": [
{"resolves": "Illuminate\\Support\\Facades\\DB", "of": "child:class"},
{"nameIn": ["select", "statement", "unprepared"]}
],
"reject": [
{"file": "*Repository.php"}
]
}
}
Der Selektor ist eine neutrale Art, die in jeder Sprache gleich gelesen wird – call, construction, function, type-declaration, assignment, loop, branch, return, throw, catch, comparison, literal, member-access, null-safe, identifier, import, parameter, block, bail-out, expression-statement, self-reference – oder kind:<Kind>, die spracheigene Knotenart (kind:Expr_MethodCall), wenn keine neutrale Art es ausdrückt.
Eine Regel wird aus anderen Regeln aufgebaut. Eine Prüfung, die Sie bereits einmal geschrieben haben, wird benannt, nie wieder geschrieben: {"rule": "ActionClass"} besteht die Prüfung, wenn die Regel dieses Namens den Knoten ebenfalls findet, und ein find, das "from": "ActionClass" anstelle von "select" schreibt, schränkt das ein, was diese Regel gefunden hat – eine Regel, die innerhalb einer anderen gelesen wird. Beide erreichen jede Regel derselben Engine in .commandments/custom/, und jeder Teil bleibt eine eigene Regel, die zu eigenen Bedingungen aufgelistet und ausgeführt wird. Ein Name, den nichts deklariert, eine Regel einer anderen Engine und eine Kette, die zu sich selbst zurückführt, werden abgelehnt, wenn die Regeln gelesen werden, bevor etwas geprüft wird.
Sagen Sie either und not ohne eine zweite Regel. {"any": [{"is": "loop"}, {"is": "branch"}]} besteht die Prüfung, wenn einer ihrer Schritte es tut, {"all": [...]} wenn jeder es tut, und {"not": {...}} wenn sein Schritt es nicht tut – so sagt ein where, was nur ein reject sagen könnte.
Die eine Prüfung eines Schrittes – jede einzelne, die es gibt, vom Tool selbst generiert:
Was es ist und wie es heißt
| Prüfung | Behält den Knoten, wenn |
|---|---|
{"is": "loop"} | er die neutrale Art beantwortet |
{"kind": "Expr_StaticCall"} | er die spracheigene Art ist, geschrieben wie die Sprache sie schreibt oder wie das Vokabular sie buchstabiert: Expr_StaticCall und static-call sind ein Konstrukt. Die Schreibweisen werden von den Arten abgeleitet, sodass die Vervollständigung des Editors auflistet, was eine Sprache wirklich ausgibt, und eine Schreibweise, die sich zwei Arten teilen, ist die Schreibweise von keiner |
{"name": "select"} | sein Name – der Name eines Aufrufs oder eines Zugriffs ist der seines Kindknotens – ist es |
{"nameIn": ["select", "statement"]} | sein Name einer von ihnen ist |
{"nameLike": "get*"} | sein Name dem Glob entspricht, * eine beliebige Zeichenfolge und ? ein Zeichen |
| `{"nameMatches": "^(get | set)[A-Z]"}` |
{"nameMood": "question"} | sein Name in diesem linguistischen Modus ist: bare-verb ist das Verb und nichts anderes (holds, ships), third-person erzählt ein Verb (entersTestMode) und question beginnt mit einem Fragewort (is, has, can, should). Er liest den Modus, niemals eine Liste von Wörtern, die eine Regel schreibt. Es ist die einzige Prüfung hier, die einen NAMEN beurteilt, und sie beantwortet nur, ob der Name AUF EINE BESTIMMTE WEISE KLINGT: was das zu einer Sünde macht, ist der Modus im Gegensatz zu dem, was das Ding TUT. Kombinieren Sie ihn also, wie es die ausgelieferten Regeln tun, mit dem, was das Mitglied beantwortet oder ob es etwas ändert |
{"nameCase": "snake"} | sein Name, ohne führendes $ oder _, ist in camel, pascal, snake, upper oder kebab-Schreibweise geschrieben |
{"hasModifier": "static"} | er den Modifikator trägt |
{"hasFlag": "byRef"} | er das Flag trägt, das der Baum seiner Sprache setzt |
{"operator": "*"} | sein Operator genau das ist: * und ?? unterscheiden zwei binäre Ausdrücke, += eine Zuweisung, ! einen unären, extends eine Vererbungsklausel |
{"operatorIn": ["*", "/"]} | sein Operator einer von ihnen ist |
Was es sagt
| Prüfung | Behält den Knoten, wenn |
|---|---|
{"text": "select * from users"} | der Text eines Literals genau das ist |
{"textLike": "*where id*"} | der Text eines Literals dem Glob entspricht |
{"textMatches": "(?i)^select"} | der Text eines Literals dem regulären Ausdruck entspricht |
{"commentIs": "history"} | ein Kommentar eine der kalibrierten Lesarten von Prosa ist: history erzählt, warum der Code GEÄNDERT wurde, anstatt was er tut, strawman verteidigt gegen einen Fehler, den niemand machen würde, und restates sagt nichts, was der Code, den es dokumentiert, nicht auch sagt. Die Lesarten sind die des Prosa-Pakets selbst, gemessen an seinen Tests in jeder Sprache, und die Menge ist abgeschlossen: Eine Regel fragt nach einem Urteil, das kalibriert wurde, niemals nach Prosa, die selbst geschriebenen Wörtern entspricht. Bei einem Kommentar ("select": "comment") beurteilt es diesen Kommentar, und bei Code beurteilt es die für ihn geschriebenen Kommentare |
{"docReference": {"resolves": false}} | ein Doc-Kommentar benennt Code, der NICHT MEHR VORHANDEN IST: Ein Verweis, den er macht (<see cref>, {@see}, @see, Sphinx :class:) löst sich zu nichts auf, und das Projekt hätte ihn besitzen müssen. Zwei Verweise werden in Ruhe gelassen – das ist der ganze Unterschied zu einem Scanner für defekte Links: einen, den der Compiler ohnehin nie hätte auflösen können, und einen, dessen Besitzer außerhalb des Scans deklariert ist, da nichts hier sagen kann, ob dieser Besitzer das Mitglied noch hat. Die Auflösung ist die der Bridge selbst |
{"commentLike": "*TODO*"} | ein Kommentar zu ihm oder in der Reihe von Kommentaren direkt darüber dem Glob entspricht – und, bei einem Kommentar selbst ("select": "comment"), was DIESER Kommentar sagt, sodass ein Schlüssel einen Kommentar liest, egal wie eine Regel ihn erreicht hat |
| `{"commentMatches": "(?i)\b(TODO | FIXME)\b"}` |
{"docTag": "deprecated"} | seine Dokumentation das Tag trägt: @tag in PHPDoc und JSDoc, ein XML-Element in C#, ein Sphinx-Feld oder eine Direktive oder ein Google/NumPy-Abschnitt in einem Python-Docstring |
{"documented": false} | es einen Doc-Kommentar trägt, oder nicht mit false |
Worauf es sich bezieht
| Prüfung | Behält den Knoten, wenn |
|---|---|
{"resolves": "App\\Models\\User"} | worauf sein Name sich bezieht, aufgelöst, das ist – oder, für einen Wert, der nichts benennt (eine Variable, eine Eigenschaft), die Klasse, die er hält: genau diese Klasse, nicht eine, die sie erweitert |
{"resolvesLike": "App\\Models\\*"} | worauf sein Name sich bezieht, aufgelöst, dem Glob entspricht, oder die Klasse, die ein Wert hält: {"resolvesLike": "*Service", "of": "child:var"} ist ein Empfänger von irgendeinem *Service |
{"declaredBy": "App\\Models\\*"} | das Mitglied, das es liest, von einer Klasse deklariert wird, die der Glob benennt: die Klasse, die sein Empfänger hält, ihren eigenen Eltern folgend, bis einer dieses Mitglied deklariert. Es ist, wessen Feld dies ist, was kein Name sagen kann, und es ist leer – der Schritt behält also nichts – wenn der Empfänger im Scan keinen Typ hat oder keine Deklaration von ihm das Mitglied deklariert |
{"answeredBy": false} | der Typ, der deklariert, was es liest, bereits die Frage benennt, die es stellt – eine Methode dieses Typs, deren gesamter Körper dasselbe zurückgibt, in beide Richtungen gelesen – oder NICHT mit false. Es ist das, was benutze den Namen, der existiert von gib ihm einen Namen unterscheidet, und eine Frage, die von genau der Methode gestellt wird, die sie benennt, ist niemals einer der Orte, die sie stellen |
{"sameAs": {"of": "closest:loop", "target": "child:iter"}} | es dasselbe benennt wie ein anderer Knoten derselben Datei, erreicht, indem man of von ihm und dann target von dort folgt – die eine Frage, die eine Regel nicht stellen könnte: ein remove auf genau der Sammlung, die seine Schleife durchläuft, ein Argument, das das Feld ist, aus dem es gerade gelesen wurde. Die beiden müssen gleich geschrieben sein, sonst als derselbe Baum gelesen werden; kein Knoten am Ende des Pfades ist kein Bestehen |
{"declaredElsewhere": true} | das, was es benennt, in einer anderen DATEI deklariert ist als der, in der es sitzt, oder nicht mit false. Ein Feld, das von außerhalb der Datei gesetzt wird, die es deklariert, hat die Methoden umgangen, die seine Klasse zum Ändern seiner selbst geschrieben hat – das ist die Frage, die eine Regel über das eigene Geschäft eines Typs stellt; es hält nichts, wenn keine Deklaration im Scan deklariert, was es benennt, also sagt eine Regel auch den Besitzer |
{"type": "number"} | der Typ, den der Ausdruck hat, wie seine Sprache ihn liest: was ein Prüfer ihm gegeben hat, was seine Form sagt, oder der Typ, den seine Deklaration schreibt – ein primitiver Typ darunter, wobei resolves eine Klasse allein beantwortet |
{"typeLike": "Money*"} | der Typ, den der Ausdruck hat, dem Glob entspricht |
{"calls": {"name": "dd"}} | ein Aufruf von ihm selbst – nicht einer innerhalb einer in ihm verschachtelten Funktion – den Schritt besteht |
{"argument": {"at": 0, "is": "literal"}} | das Argument an dieser Stelle den Schritt besteht; ein negatives at zählt vom Ende, -1 das letzte, und keines dort besteht nichts. Ohne at genügt JEDES Argument, das den Schritt besteht – so fragt eine Regel nach einem, das namentlich übergeben wird, dessen Position nicht identifiziert |
{"constructs": "Date*"} | eine Konstruktion, oder bei allem anderen eine von ihm selbst, eine Instanz eines Typs erstellt, den der Glob benennt – in Python ein Aufruf einer Klasse |
{"unused": true} | nichts darauf verweist: eine Funktion oder ein Typ von außerhalb seiner selbst, durch die Namen und Aufrufe, die der Scan auflöst, und den Aufrufgraphen seiner Sprache; ein Parameter, durch seinen in seiner Funktion gelesenen Namen; ein Feld oder eine Eigenschaft, durch das, was seine Deklaration durch den Typ erreicht, den sein Empfänger hält, da ein Feld in den meisten Sprachen auf kein Symbol verweist. Ein Mitglied, das die Sprache selbst namentlich erreicht, und eines, das überschreibt, sind nicht dies: Nichts, was das Projekt schreibt, muss sie benennen |
{"referredFrom": {"is": "parameter"}} | etwas, das darauf verweist – ein Aufruf, ein Name, ein geschriebener Typ – den Schritt besteht. Es ist calledFrom, das nach der REFERENZ fragt, nicht nach der Datei, in der es sitzt – das unterscheidet eine Seite einer Sache von einer anderen: Eine Ereignisklasse, die von einem Dispatch und von keiner Listener-Parameter genannt wird, wird von niemandem gehört, und {"not": {"referredFrom": …}} sagt genau das |
{"calledFrom": "app/Http/**"} | etwas, das darauf verweist – meist ein Aufruf – in einer Datei sitzt, die der Glob findet, gelesen wie file einen liest |
Wo es sitzt
| Prüfung | Behält den Knoten, wenn |
|---|---|
{"file": "*Repository.php"} | sein Pfad vom Projekt (dem Repository, in dem es liegt, sonst dem geprüften Ordner) oder ein beliebiges Ende davon dem Glob entspricht: * bleibt innerhalb eines Ordners und ** kreuzt sie, wie in .gitignore |
| `{"fileMatches": "(^ | /)pages/.*(Create |
{"namespaceLike": "App\\Http\\*"} | der Namespace, das Paket oder Modul, in dem es deklariert ist, dem Glob entspricht |
{"layer": "App\\Domain"} | es in dieser Ebene des Stacks sitzt, den das Projekt deklariert (Backend, Python, C#) |
{"topLevel": true} | es außerhalb jeder Funktion, auch Closures, und jedes Typs sitzt – Code, der beim Laden seiner Datei ausgeführt wird |
{"withinLoop": true} | es innerhalb einer Schleife sitzt, oder nicht mit false |
{"position": "first"} | es der first, last oder only seiner Geschwister ist (ein einziges ist auch first und last) |
{"field": "cond"} | es diesen Slot seines Elternteils im Baum füllt – der spracheigene Name für den Slot, wie cond, body, else, args – das ist die eine Achse des Ortes, die die anderen Prüfungen nie fragen: ein Aufruf, der ALS Bedingung steht, nicht einer, der als Argument übergeben wird. Die Namen sind die seiner Sprache, also prüft eine damit geschriebene Regel diese Sprache |
{"role": "member"} | es diese Art von Ding ist, in den Worten, die jede Bridge schreibt: statement, expression, member (jede Deklaration eines Typs, einer Funktion, einer Eigenschaft oder eines Enum-Cases), type (ein Typ, wie geschrieben), pattern, markup oder other. So sagt eine Regel eine Deklaration, ohne eine Art pro Sprache zu nennen – das braucht eine Regel über die Ordnung oder die Gesellschaft von Mitgliedern |
{"descendant": {"is": "return"}} | ein Knoten darin den Schritt besteht; mit "within": "ownBody" werden nur die Knoten betreten, die es SELBST hält, und stoppen bei einer darin verschachtelten Funktion – das ist der Unterschied zwischen einer Methode, die speichert, und einer, die einen Closure, der das Speichern hält, an eine Warteschlange übergibt |
{"child": {"is": "return"}} | ein Knoten eine Ebene darin den Schritt besteht, was descendant in beliebiger Tiefe nicht sagen kann: eine Funktion, deren eigener Körper zurückgibt, und nichts für eine, die nur innerhalb eines if zurückgibt. Mit "field" werden nur die Kinder betreten, die diesen Feld des Baumes füllen, so wie das Feld einer Zählung das einschränkt, was sie zählt |
{"body": {"is": "branch"}} | eine Anweisung seines KÖRPERS den Schritt besteht, gelesen durch das, was seine Sprache einen Körper einhüllt: C# schreibt eine Blockanweisung und Python schreibt nackte Anweisungen, also antwortet child ihnen unterschiedlich und dies antwortet ihnen gleich. So wird eine Regel über eine Schleife oder den Körper einer Funktion einmal für jede Sprache geschrieben |
{"inside": {"is": "catch"}} | ein Knoten über ihm, bis zur Wurzel der Datei, den Schritt besteht |
| `{"all": [{"name": "store"}, {"hasModifier": "pu |
… (gekürzt für die Übersetzung)