NoVerify: Ein PHP-Linter, der schnell arbeitet

Es gibt gute Dienstprogramme zur statischen Analyse für PHP: PHPStan, Psalm, Phan, Exakat. Linters machen ihre Arbeit gut, aber sehr langsam, da fast alle in PHP (oder Java) geschrieben sind. Für den persönlichen Gebrauch oder ein kleines Projekt ist dies normal, aber für eine Site mit Millionen von Benutzern ist dies ein kritischer Faktor. Ein langsamer Linter verlangsamt die CI-Pipeline und macht es unmöglich, sie als Lösung zu verwenden, die in einen Texteditor oder eine IDE integriert werden kann.



Eine Site mit Millionen von Benutzern ist VKontakte. Entwicklung und Hinzufügung neuer Funktionen, Testen und Beheben von Fehlern, Überprüfungen - all dies sollte unter den Bedingungen harter Fristen schnell gehen. Daher ist ein guter und schneller Linter, der die Codebasis in 5-10 Sekunden auf 5 Millionen Zeilen überprüfen kann, unersetzlich. 

Es gibt keine geeigneten Linters auf dem Markt, so Yuri Nasretdinov (youROCK) von VKontakte schrieb seine Hilfe an Entwicklungsteams - NoVerify. Dies ist ein Linter für PHP, der in Go geschrieben ist. Es funktioniert 10 bis 30 Mal schneller als Analoga, kann etwas finden, vor dem PhpStorm nicht warnt, erweitert sich leicht und lässt sich gut in Projekte integrieren, in denen sie noch nie von statischen Analysen gehört haben. Iskander Sharipov

wird über diesen Linter berichten . Unter der Katze: Wie man einen Linter auswählt und lieber einen eigenen schreibt, warum NoVerify so schnell ist und wie er im Inneren angeordnet ist, warum er in Go geschrieben ist, was er finden und wie er sich ausdehnt, welche Kompromisse Sie eingehen mussten und was auf seiner Basis aufgebaut werden kann.


Iskander Sharipov (Quasilyte) arbeitet in der Backend-Infrastruktur von VKontakte und ist mit NoVerify gut vertraut. In der Vergangenheit war er im Go-Compiler des Intel-Teams beschäftigt. Er schreibt nicht in PHP, aber dies ist seine Lieblingssprache für statische Analysen - es gibt viele Dinge, die schief gehen können.

Hinweis. Um den Hintergrund zu verstehen, lesen Sie den Artikel von Yuri Nasretdinov, dem Autor von NoVerify on Habré, mit einem Hintergrund und einem Vergleich mit einigen vorhandenen Lintern, die normalerweise in PHP geschrieben sind. Alle Aussagen in Richtung PHP (im Artikel von Yuri und hier) sind ein Witz. Iskander liebt PHP, jeder liebt PHP.

Produktentwicklung


In VKontakte ist dies eine Website-Entwicklung auf KPHP. Geschwindigkeit ist für VKontakte wichtig: Beheben von Fehlern, Hinzufügen und Entwickeln neuer Funktionen von der ersten bis zur letzten Phase. Aber Geschwindigkeit geht mit Fehlern einher , besonders wenn es harte Fristen gibt - wir haben es eilig, sind nervös und machen mehr Fehler als in einer ruhigen Situation.

Fehler betreffen Benutzer . Wir wollen nicht, dass sie leiden, deshalb kontrollieren wir die Qualität. Die Qualitätskontrolle verlangsamt jedoch die Entwicklung . Dies wollen wir auch nicht, daher muss der Effekt minimiert werden.

Um dies zu tun, könnten wir führen mehr Code Bewertungen Unfehlbar mieten mehr Testerund schreibe weitere Tests. All dies ist jedoch schlecht automatisiert: Die Überprüfung muss durchgeführt und die Tests müssen geschrieben werden.

Die Hauptaufgaben meines Teams sind unterschiedlich.

Sammeln Sie Metriken, analysieren Sie sie und reparieren Sie sie schnell . Wenn etwas schief gelaufen ist, möchten wir es schnell zurücksetzen, verstehen, was falsch ist, es beheben und den Arbeitscode schnell wieder in die Produktion einfügen.

Überwachen Sie die Genauigkeit der Pipeline, damit nicht funktionierender Code überhaupt nicht in Produktion geht - Sie müssen ihn nicht zurücksetzen. Hier kommen Linters zur Rettung - statische Code-Analysatoren. Wir werden darüber reden.

Wähle einen Linter


Wählen wir einen Linter, den wir der Pipeline hinzufügen. Wir verfolgen einen einfachen Ansatz - wir formulieren die Anforderungen.

Linter sollte schnell arbeiten . Unsere Pipeline besteht aus mehreren Schritten: Der Betrieb des Linter sollte nicht viel Zeit und zeitaufwändigen Entwickler erfordern, während er auf Feedback wartet.

Unterstützung für "ihre" Schecks . Höchstwahrscheinlich hat der Linter nicht alles, was wir brauchen - wir müssen unsere eigenen Schecks hinzufügen. Sie sollten Probleme finden, die für unsere Codebasis typisch sind, und den Code aus der Sicht unseres Projekts überprüfen. Nicht alles kann (oder bequem) durch Tests abgedeckt werden.

Unterstützung für "eigene" Schecks. Wir können viele Tests schreiben, aber werden sie gut unterstützt? Wenn wir beispielsweise über reguläre Ausdrücke schreiben, werden diese komplizierter, wenn Sie den Kontext, die Semantik und die Syntax der Sprache berücksichtigen müssen. Daher sind Tests keine Option.

Die meisten der von uns überprüften Linters sind in PHP geschrieben. Sie geben aber nicht weiter. Linters in PHP (es gibt noch keine AOT-Kompilierung) arbeiten 10 bis 20 Mal langsamer als in anderen Sprachen - unsere größte Datei kann mehrere zehn Sekunden lang analysiert werden. Dies verlangsamt den Workflow zu sehr und zu sehr - dies ist ein schwerwiegender Fehler . Was machen Entwickler in diesem Fall? Sie schreiben ihre eigenen.

Deshalb haben wir unseren NoVerify PHP Linter on Go geschrieben. Warum darauf? Spoiler: Nicht nur, weil Jura es so entschieden hat.

Noverify


Go ist ein guter Kompromiss zwischen Entwicklungsgeschwindigkeit und Produktivität.
Der erste "Beweis" im Bild mit "Infografiken": gute Ausführungsgeschwindigkeit, einfache Unterstützung. Wir verlieren an Entwicklungsgeschwindigkeit, aber die ersten beiden Punkte sind uns wichtiger.


Die Figuren werden aus dem Kopf genommen, sie werden von nichts gestützt.

Für den zweiten „Beweis“ argumentierte er einfacher.


PHP ist langsamer, Go ist schneller und so weiter. 

Wir haben uns aus drei Gründen für Go entschieden.

Go als Sprache für Versorgungsunternehmen ist auf einer grundlegenden Ebene leicht zu erlernen . Im PHP-Entwicklungsteam hat sicher jemand von Go gehört, Docker angeschaut, weiß, dass es in Go geschrieben ist, vielleicht sogar die Quelle gesehen. Mit einem Grundverständnis können sie nach ein oder zwei Wochen intensiven Go-Lernens Code darauf schreiben.

Go ist sehr effektiv . Selbst ein Anfänger wird nicht viele Fehler machen können, weil Go eine gute Stimmung und viel Linter hat. In Go ist der durchschnittliche Code etwas besser als in anderen Sprachen, da es viel weniger Möglichkeiten gibt, das eigene Bein zu schießen.

Go-Apps sind einfach zu warten.Go ist eine ziemlich ausgereifte Programmiersprache, für die fast alle Entwickler-Tools verfügbar sind, die Sie sich wünschen können.

Wir werden NoVerify mit unseren Anforderungen überprüfen.

  • NoVerify ist um ein Vielfaches schneller als Alternativen .

  • Dafür können Sie sowohl Open Source- als auch eigene Erweiterungen schreiben . Es ist wichtig, dass wir diese Prüfungen trennen können und Sie Ihre eigenen schreiben können.
  • Einfach zu testen und zu entwickeln. Zum Teil, weil die Standard-Go-Distribution über ein Standard-Framework mit Profilerstellung und Tests verfügt. Es wird hauptsächlich für Unit-Tests verwendet. Besonders geschickt kann man wie wir für die Integration verwenden - wir haben Integrationstests, die durch Go-Tests geschrieben wurden.

Kompromiss der Integration


Beginnen wir mit dem Problem. Wenn Sie zum ersten Mal einen Linter für ein altes Projekt starten, für das keine Analyse verwendet wurde, wird dies höchstwahrscheinlich angezeigt.



Oh mein Code! Niemand wird jemals so viele Fehler korrigieren. Ich möchte das Projekt schließen, den Linter entfernen und ihn nie wieder ausführen. Was tun, um dies zu vermeiden?

Integrieren


Im Diff-Modus ausführen . Wir möchten nicht alle Überprüfungen des gesamten Projekts mit einer Million Fehlern bei jedem CI-Schritt durchführen. Vielleicht kennen Sie die Baseline: In NoVerify ist dies sofort einsatzbereit. Sie müssen kein separates Dienstprogramm einbetten. Wir waren sofort der Ansicht, dass ein solches Regime erforderlich ist.

Fügen Sie den Ausnahmen Legacy (Anbieter) hinzu . Es ist einfacher, einige Dinge nicht zu berühren, selbst bei einem Defekt beiseite zu lassen, um ihn nicht selbst zu modifizieren und keine Spuren in der Geschichte zu hinterlassen.

Wir beginnen mit einer Teilmenge von Schecks . Sie können nicht alles verbinden, was Stil beinhaltet. Zunächst werden echte Fehler gefunden: Wir finden, beheben und wechseln zu etwas Neuem.

Wir sammeln Feedback von Kollegen. Wie kann man verstehen, wann es Zeit ist, etwas anderes einzuschalten? Fragen Sie Kollegen. Sobald sie froh sind, dass die Fehler verschwunden sind und fast nichts gefunden wurde, schalten Sie etwas anderes ein - es ist Zeit zu arbeiten.

Git-Setup


Diff-Modus bedeutet, dass Sie ein Versionskontrollsystem haben - Git. Wenn Sie SVN haben, hilft die Anweisung nicht weiter, gehen Sie zu Git.

Wir installieren den Pre-Push-Haken mit einem Linter und prüfen, bevor wir den Code starten. Wir überprüfen den lokalen Computer mit der Option --no-verify, den Linter zu umgehen . Es wäre wahrscheinlich bequemer, einen Pre-Receive-Hook zu verwenden und den serverseitigen Linter zu deaktivieren, aber aus historischen Gründen passieren in VK viele Dinge in einem Pre-Push-Hook, sodass auch NoVerify eingebaut wurde.

Nach dem Push werden CI-Prüfungen gestartet. NoVerify verfügt über zwei Betriebsarten: mit vollständiger Analyse und ohne. Auf CI möchten (und können) Sie höchstwahrscheinlich ausführen--git-full-diff- Maschinen in CI können härter geladen werden und überprüfen auch die Dateien, die sich nicht geändert haben. Auf lokalen Computern können wir eine weniger strenge, aber schnellere Analyse nur geänderter Dateien ausführen (5-15 Sekunden schneller). 

Fehlalarm




Betrachten Sie das folgende Beispiel: In dieser Funktion wird etwas akzeptiert, das ein Feld enthält, aber der Typ wird in keiner Weise beschrieben. Es ist keine Tatsache, dass es überhaupt ein Feld gibt, wenn eine Funktion aus verschiedenen Kontexten aufgerufen wird. In einer strengen Version könnte sich der Linter beschweren: "Es ist nicht klar, um welchen Typ es sich handelt, wie kann ich ein Feld ohne Prüfung zurückgeben?" Dies ist jedoch nicht unbedingt ein Fehler.

function get_foo($obj) {
    return $obj->foo;
    ^^^
}

Warning:
Property "foo" does not exist

False Positives stören. 
Dies ist der Hauptgrund für den Verzicht auf Linter. Menschen wählen andere Optionen, die weniger Fehler finden, aber weniger Fehlalarme erzeugen.

Sie schubsen oft herum, wenn etwas funktioniert, aber das ist kein Fehler. Viele haben einen Mechanismus, um Linter zu umgehen - um mit der Flagge zu laufen, ohne den Linter zu überprüfen. In unserem Land wurde diese Flagge als no-verifyPre-Push-Haken bezeichnet. Wir haben es oft benutzt und der Name wurde im Namen des Linter verewigt.

Pickiness


Eine weitere Linter-Eigenschaft. Zum Beispiel verstehen viele Aliase nicht. In PHP ist sizeofdies ein Analogon count: Es berechnet nicht die Größe, sondern gibt die Anzahl der Elemente zurück. Das mentale Modell der C-Entwickler hat eine sizeofandere Bedeutung. Wenn es in der Codebasis sizeofhöchstwahrscheinlich einen Mittelwert gibt count. Aber das ist ein Trottel.

$len = sizeof($x);
    ^^^^^^

Warning:
use "count" instead of "sizeof"

Was soll man damit machen?


Sei streng und zwinge alles ausnahmslos zu regieren . Regeln auferlegen, fordern, beachten und nicht umgehen lassen - funktioniert nie. Damit eine solche Starrheit funktioniert, muss das Team aus denselben Personen bestehen: Charakter, Kulturniveau, Pedanterie und Wahrnehmung der Qualität des Codes. Wenn dies nicht so ist, wird es einen Aufstand geben. Es ist einfacher, ein Team aus Ihren Klonen zusammenzustellen, als alle Regeln zu befolgen. 

Blockieren Sie nicht Push / commit auf Kommentare wie Fixierung sizeofauf count. Dies ist höchstwahrscheinlich kein Fehler, sondern eine Fehlauswahl und wirkt sich nicht auf den Code aus. Aber dann werden 99% der Antworten (vom Team) ignoriert und es werden immer zusätzliche im Code sein sizeof.

Ermöglichen Sie eine gewisse Konfigurationsebene für verschiedene Teams und Entwickler.Sie können die Konfiguration für jeden Befehl so konfigurieren, dass diejenigen, die nicht zu ändern möchten sizeof, countdies nicht tun können. Lassen Sie alle anderen die Regeln befolgen. Eine gute Option, aber die Konsistenz sinkt, und in einigen Verzeichnissen ist der Code etwas schlechter.

Führen Sie solche Überprüfungen einmal im Monat auf Subbotniks durch . Überprüfungen können nicht jedes Mal am CI oder Pre-Push-Haken durchgeführt werden, sondern einmal im Monat in der Routine Cron. Führen Sie alles aus, was Sie nach der aktiven Entwicklung finden, und bearbeiten Sie es. Diese Arbeit erfordert jedoch Ressourcen für die Automatisierung und Überprüfung.

Nichts tun. Das Deaktivieren von Stilprüfungen ist ebenfalls eine Option.

Kompromiss




Es wird immer einen Kompromiss zwischen einem glücklichen Entwickler und einem glücklichen Linter geben. Es ist leicht, einen Linter glücklich zu machen: der strengste Modus und das Fehlen von Problemumgehungen. Vielleicht bleibt danach niemand mehr im Team. Wenn der Linter die Arbeit stört, ist dies ein Problem.
Vor allem nützliche Maßnahmen.

NoVerify Technische Details


Private Schecks VKontakte. Noverify ist so etwas geschrieben. In GitHub ist das NoVerify-Repository in zwei Teile unterteilt: das Framework, mit dem der Linter implementiert wird, und separate Überprüfungen, vklints . Dies geschieht, damit der Linter Schecks von Drittanbietern lädt: Sie können ein separates Modul auf Go schreiben und diese registrieren sich im Framework. Nach dem Start von der NoVerify-Binärdatei lädt das Framework alle registrierten Überprüfungssätze und sie funktionieren als Ganzes. 



NoVerify ist sowohl eine Bibliothek als auch eine Binärdatei (Linter).

Unsere Schecks heißen vklints . Sie stellen fest, dass PhpStorm und Open Source NoVerify nicht angezeigt werden - wichtige Fehler, die nicht für den allgemeinen Gebrauch geeignet sind.

Was ist vklints?

Überprüfen der Besonderheiten der Verwendung bestimmter Funktionen , Klassen und sogar globaler Variablen, die nicht unseren Konventionen entsprechen. Dies kann aus verschiedenen Gründen, die im Styleguide beschrieben sind, nicht an bestimmten Orten verwendet werden.

Zusätzliche Stilprüfungen. Sie entsprechen nicht dem, was in der PHP-Community akzeptiert wird, sind in der PHP-Standardempfehlung nicht beschrieben oder widersprechen ihr sogar, aber für uns ist es der Standard. Es macht keinen Sinn, sie zu Open Source hinzuzufügen, da Sie ihnen nicht folgen möchten.

Strenge Vergleichsanforderungen für einige Typen . Zum Beispiel haben wir eine Prüfung, bei der Zeichenfolgen mit einem Vergleichsoperator verglichen werden müssen ===. Insbesondere muss ein Flag zum strikten Vergleich von Funktionen übergeben werden, um Zeichenfolgen zu vergleichen.

Verdächtige Array-Schlüssel.Ein weiterer interessanter Fehler: Manchmal können Entwickler beim Festschreiben Tastenkombinationen drücken, bevor sie die Datei speichern. Diese Zeichen verbleiben manchmal in einer Zeichenfolge oder in einem Code. Einmal befand sich im Schlüssel des Arrays der russische Buchstabe „Y“. Höchstwahrscheinlich hat der Entwickler im russischen Layout STRG-S gedrückt, die Datei gespeichert und festgeschrieben. Manchmal finden wir solche Schlüssel in Arrays, aber neue Fehler werden nicht mehr übertragen.

Dynamische Regeln sind ein einfacherer NoVerify-Erweiterungsmechanismus, der in PHP beschrieben wird. Dazu wurde ein separater Artikel geschrieben: Wie man NoVerify Schecks hinzufügt, ohne eine einzige Zeile Go-Code zu schreiben .

So funktioniert NoVerify


Um PHP zu analysieren, benötigen Sie einen Parser . Wir können den PHP-Parser in PHP nicht verwenden: Er ist langsam, von Go aus kann er nur über einen Wrapper in C verwendet werden. Daher verwenden wir den Parser in Go.

Dieser Parser hat mehrere Probleme. Leider kann er nur mit UTF-8 arbeiten und wir müssen zwischen UTF-8 und nicht UTF-8 unterscheiden . Neben UTF-8 ist Windows-1251 häufig in russischen PHP-Projekten enthalten. Wir haben auch solche Dateien. Woran erkennen wir sie? 

Die Datei encodings.xmllistet alle Pfade auf, in denen sich die Dateien mit UTF-8 befinden. Wenn wir auf eine Datei außerhalb dieser Pfade stoßen, streamen wir im laufenden Betrieb per Streaming-Stream zu UTF-8 (ohne vorher zu konvertieren).


Analyse und Analyse


In wenigen Schritten abgeschlossen. Zum einen laden wir Metadaten von phpstorm-stubs . Dies sind Daten, die wie PHP-Code aussehen, aber niemals ausgeführt werden und die Arten von Ein- / Ausgängen von Standardfunktionen beschreiben. Die phpStorm-Metadaten haben eine nützliche Override- Direktive für den Linter ... Sie können beispielsweise beschreiben, dass wir ein Array vom Typ akzeptieren T[]und den Typ zurückgeben (nützlich für Funktionen array_pop).


Phpstorm-stubs wird zuerst geladen. Wir verwenden Metadaten als anfängliche Typinformationen - die Grundlage. Diese Grundlage wird vom Linter absorbiert und wir beginnen mit der Analyse der Quellen.

Wir laden den aktuellen Master vor der Absorption. Wir überprüfen den Code in zwei Modi:

  • lokale Änderungen : In Bezug auf die Grundlinie finden wir neue Fehler im Code;
  • Wir geben den Bereich der Revisionen an : die erste und die letzte Revision, und zwischen ihnen ist alles inklusive - dies ist der neue Code und alles, was „vorher“ alt ist.

Als nächstes folgt die Analysephase.



AST-Analyse . Wir haben jetzt Metadaten, Typinformationen. Wir nehmen die gesamte PHP-Quelle, analysieren und analysieren direkt über dem AST - wir haben im Moment keine Zwischendarstellung. Das Analysieren eines unformatierten AST ist nicht sehr praktisch, insbesondere wenn Sie von den darin enthaltenen Bibliotheken und Datentypen abhängen. 



Die Analyseergebnisse werden im Cache gespeichert . Es wird in der Reanalyse verwendet, die viel schneller ist.

Berichte und Filterung . Dann generieren wir zweimal Berichte oder Warnungen : Zuerst finden wir Warnungen für die alte Version des Codes (vor der Baseline), dann für die neue. Berichte werden nach Vergleich gefiltert (diff) - wir suchen nach Warnungen, die in der neuen Version des Codes enthalten sind, und geben sie an den Benutzer weiter. In einigen statischen Analysegeräten wird dies als "Basismodus" bezeichnet.



Die Doppelcode-Analyse (im Diff-Modus) ist sehr langsam. Aber wir können es uns leisten - NoVerify ist immer noch Dutzende Male schneller als andere PHP-Linker. Gleichzeitig verfügt es über eine Reserve für zusätzliche Beschleunigung von mindestens 30 Prozent.

Wie analysieren wir Dateien? In PHP können Sie eine Funktion aufrufen, bevor sie definiert wird. Sie müssen die Informationen zu dieser Funktion kennen, bevor Sie sie analysieren können. Daher gehen wir zuerst die gesamte Datei in AST durch, indexieren, identifizieren die Typen aller Funktionen, registrieren die Klassen und analysieren sie erst dann. 



Die Analyse ist der zweite Durchgang durch die Datei . Die meisten Interpreten und Compiler arbeiten auch mit zwei Durchgängen und mehr. Um die Datei nicht ein zweites Mal zu "scannen", müssen Sie vor der Verwendung Deklarationen haben, wie beispielsweise in C.

Typinferenz


Das Interessanteste ist, dass hier am häufigsten Fehler auftreten. Es entspricht immer noch nicht der Korrektheit des PHP-Typsystems, das formal schwer zu definieren ist.

Wie sieht das Modell aus?


Semantisches Modell (Demo).

Arten von Typen:

  • Erwartet wird das, was wir in den Kommentaren beschreiben. Wir erwarten einige Typen im Programm, aber dies bedeutet nicht, dass sie wirklich darin verwendet werden.
  • Tatsächlich - echte, die im Programm sind. Wenn wir beispielsweise etwas eine Nummer zuweisen, ist es offensichtlich, dass intoder float(wenn dies eine Gleitkommazahl ist) tatsächliche Typen sind. 

Tatsächliche Typen scheinen "stärker" zu sein - sie sind real, wahr. Aber manchmal können wir einen Typ nur durch Annotation erhalten.

Anmerkungen (in erwarteten Typen) können in zwei Kategorien unterteilt werden: Vertrauen und Misstrauen . Zum Beispiel gehören phpstorm-stubs zur ersten Kategorie. Sie gelten als moderiert (ohne Fehler), bevor wir sie verwenden. Unzuverlässige sind diejenigen, die andere Entwickler schreiben, weil sie möglicherweise Fehler aufweisen.

Tatsächliche Typen können auch in mehrere Teile unterteilt werden: Werte, Zusicherungen, Prädikate und Typhinweise, wodurch die Funktionen von PHP 7 erweitert werden. Es gibt jedoch ein Problem, das durch Typhinweise nicht gelöst werden kann.

Erwartet gegen Ist


Angenommen, eine Klasse Foohat einen Erben. Aus der Nachkommenklasse können wir Methoden aufrufen, die nicht in Foo enthalten sind, da der Nachkomme das übergeordnete Element erweitert. Aber wenn wir das Erbe bekommen Foovon new static()dieser Anmerkung des Rückgabetypen (aus self), dann wird ein Problem auftreten. Wir können diese Methode aufrufen, aber die IDE fordert Sie nicht dazu auf - Sie müssen angeben static(). Dies ist eine späte statische Bindung in PHP , wenn der FooKlassenvererb nicht zurückkehren kann

class Foo {
    /** @return static */
    public function newStatic() : self {
        return new static();
    }
}
// actual = Foo
// expected = static

Wenn wir schreiben new static(), kann nicht nur die Klasse zurückkehren new Foo. Wenn beispielsweise eine FooKlasse geerbt wird bar, kann dies der Fall sein new bar. Dementsprechend benötigen wir mindestens zwei Typinformationen. Keiner von ihnen ist redundant - beide werden benötigt.

Daher ist der tatsächliche Typ hier self- für den PHP-Interpreter. Aber damit die IDE und die Linters funktionieren, brauchen wir static. Wenn wir diesen Code aus dem Kontext der Erbenklasse aufrufen, müssen wir die Information kennen, dass dies nicht dieselbe Basisklasse ist und mehr Methoden hat.

class Foo {
    /** @return static */
    public function newStatic() : self {
        return new static();
    }
}
// actual -  PHP 
// expected -   IDE/

Tipp Hinweis


Statische Typisierung und Typhinweis sind nicht dasselbe.
Sie haben vielleicht gehört, dass Sie nur an den Grenzen von Funktionen überprüfen können. An den Rändern überprüfen wir die Ein- und Ausgänge, wobei die Eingabe die Funktionsargumente sind. Innerhalb der Funktion können Sie jeden Unsinn machen: Weisen Sie einen fooWert zu int, obwohl Sie beschrieben haben, was es ist T. Sie können sich beschweren, dass Sie die von Ihnen deklarierten Typen verletzen, aber für PHP gibt es keinen Fehler

declare(strict_types=1);
    function f(T $foo) {
        $foo = 10; //  int
        return $foo;
}

Ein Beispiel ist schwieriger - wir kehren zurück foo? Zu Beginn der Funktion haben wir festgestellt, dass foodies der Fall ist T, und es gibt keine Informationen über die Rückgabe. 

declare(strict_types=1);
function f(T $foo) {
    $foo = 10; //  int
    return $foo;
}
// ? 1. f -> int
// ? 2. f -> T|int
// ? 3. f -> T

Welcher Typ ist richtig? Die ersten beiden werden wir den Unterschied zwischen ihnen analysieren. PhpStorm und Linter geben die zweite Option aus. Trotz der Tatsache, dass es immer zurückkehrt int, wird der Typ abgeleitet T|int- die "Vereinigung" der Typen. Dies ist ein Typ, dem beide Werte zugewiesen werden können: Zuerst hatten wir Informationen über den Typ T, dann haben wir sie zugewiesen 10, sodass der Typ der Variablen foomit diesen beiden Typen kompatibel sein muss.

Anmerkungen


Kommentare und Anmerkungen können lügen.
Im folgenden Beispiel haben wir geschrieben, dass wir eine Zahl, aber eine Zeichenfolge zurückgeben. Wenn der Linter nur auf der Ebene von Anmerkungen und Typhinweisen funktioniert, wird davon ausgegangen, dass er immer zurückkehrt int. Tatsächliche Typen helfen jedoch nur dabei, sich davon zu lösen: Hier ist der erwartete Typ der folgende intund der tatsächliche Typ eine Zeichenfolge. Linter weiß, dass die Zeichenfolge zurückgegeben wird, und warnt Sie möglicherweise, dass Sie die Rückgabe versprochen haben int. Diese Trennung ist uns wichtig.

/** @return int */
function f() { return "I lied!"; }

Vererbung von Anmerkungen. Hier meine ich, dass eine Klasse, die eine Art Schnittstelle implementiert, eine Methode hat. Die Methode hat gute Kommentare, Dokumentation, Typen - es wird benötigt, um die Schnittstelle zu implementieren. Es gibt jedoch keine Kommentare in der Implementierung: Es gibt nur @inheritdocoder gar nichts.

interface IFoo {
    /** @return int */
    public function foo();
}
class Fooer implements IFoo {
    /** @inheritdoc */
    public function foo() { return "10"; }
}

Was gibt diese Methode zurück? Es scheint, dass das, was in der - Schnittstelle beschrieben wird, zurückgegeben wird int, aber tatsächlich eine Zeichenfolge. Das ist nicht gut: PHP ist egal, aber Konvergenz ist uns wichtig.

Es gibt zwei Möglichkeiten, diesen Code zu reparieren. Das Offensichtliche ist, zurückzukehrenint . Aber vielleicht müssen Sie einen anderen Typ zurückgeben. Was zu tun ist? Schreiben Sie, dass wir den String zurückgeben . In diesem Fall sind explizite Typinformationen sowohl für die IDE als auch für den Linter erforderlich, um den Code korrekt zu analysieren.

interface IFoo {
    /** @return int */
    public function foo();
}
class Fooer implements IFoo {
    /** @return string */
    public function foo() { return "10"; }
}

Diese Informationen würden überhaupt nicht benötigt, wenn Leute Kommentare schreiben würden, nicht @inheritdoc. PhpStorm muss nicht verstehen, welche Typen Sie haben. Wenn die Typen jedoch nicht korrekt beschrieben werden, liegt ein Problem vor.

PhpStorm und der Linter haben eine Reihe von disjunkten Fehlern, wenn wir dieselben Dateien für Metadaten (Typen) verwenden. Wenn wir alles, was wir brauchen, in phpstorm-stubs aus dem JetBrains-Repository reparieren, wird die IDE höchstwahrscheinlich kaputt gehen. Wenn Sie standardmäßig alles belassen, funktioniert

bei uns nicht alles richtig . Daher haben wir eine kleine Gabel -  VKCOM / phpstorm-stubs . Es wurden einige Patches hinzugefügt, um etwas zu beheben, das nicht passt. Ich kann es für PhpStorm nicht empfehlen, aber es ist notwendig, damit der Linter funktioniert.

Open Source


Noverify ist ein Open Source-Projekt. Es ist auf GitHub veröffentlicht .

Kurze Anweisungen "Wenn etwas schief gelaufen ist."

Wenn etwas kaputt ist oder nicht startet. Die falsche Reaktion besteht darin, NoVerify erneut zu senden und zu entfernen. Die richtige Reaktion: ein Ticket auf GitHub ausstellen und über Ihr Problem sprechen. Höchstwahrscheinlich wird es in 1-2 Tagen behoben sein.

Ihnen fehlt eine Funktion. Falsche Reaktion: Entfernen Sie NoVerify und schreiben Sie Ihren eigenen Linter (obwohl das Schreiben Ihres eigenen Linter immer cool ist). Die richtige Reaktion: Um ein Ticket auf GitHub auszustellen und vielleicht werden wir eine neue Funktion hinzufügen. Es ist komplizierter mit Funktionen als mit Fehlern - es entsteht eine Diskussion, und jede Person hat eine andere Vision von der Implementierung im Team. Aber am Ende werden sie noch umgesetzt.

Wenn Sie an der Entwicklung des Projekts interessiert sind oder nur über statische Analysen sprechen möchten, besuchen Sie unseren Chatraum - noverify_linter .

PHP-, , , , PHP Russia.

, . , , . telegram- @PHPRussiaConfChannel. , .

Source: https://habr.com/ru/post/undefined/


All Articles