Hallo und erneut willkommen. Hier spricht . Heute möchte ich mit Ihnen über ein häufiges Problem sprechen – was zu tun ist, wenn ein Projekt von mehreren Entwicklern auf unterschiedlichen Kenntnisstufen, wie zum Beispiel bei einer Angular-Anwendung, geschrieben wird.

Es hat sich so ergeben, dass ich lange Zeit nur mit meinem Team gearbeitet habe, wo wir bereits klare Regeln für Formatierung, Kommentierung, Abstände usw. festgelegt hatten. Wir haben uns daran gewöhnt und lebten harmonisch und glücklich. Aus Freude darüber habe ich sogar einen Artikel auf Habr veröffentlicht über . Daher haben wir etwas Magisches genutzt, nämlich nur TSLint für Pre-Commits.
Dann sind wir gewachsen. Ein neues Projekt mit vererbtem Code kam hinzu, und zusammen mit ihm vier neue Entwickler. Und dann lief es nicht nach Plan.

Ich denke, viele wissen, dass die Arbeit mit vererbtem Code nicht angenehm ist. Von all den Projekten, die ich erlebt habe, war nur eines wirklich großartig, der Rest... Worüber habe ich gesprochen? Ah ja.
Um ehrlich zu sein, ließ die Architektur des Projekts zu wünschen übrig, und Kommentare sowie Typisierung waren für uns nur ein Traum. Irgendwann war ich frustriert, weil unsere Dokumentation nicht den Formatierungsrichtlinien entsprach, Kommentare nicht erstellt wurden, und die Typisierung - was sollte das eigentlich sein?). Damit musste etwas unternommen werden.
Für diejenigen, die nicht länger auf die Schritte warten möchten:
Wir haben tslint in sanfte Regeln (für pre-commit) und strenge Regeln (für die IDE, um daran zu erinnern, was die Entwickler vergessen haben) aufgeteilt.
Wir haben auto-fixing möglicher Regeln von strengen tslint für pre-commit implementiert.
Wir haben Regeln für prettier erstellt.
Wir haben alles Mögliche versucht, um ng lint mit lint-staged zum Laufen zu bringen.
Schritt eins – teile und herrsche.
Als mir die Idee kam, die Regeln des Linters zu verschärfen, dachte ich, dass wir scheitern würden. Der Code ist schließlich vererbt. Man muss sich damit auseinandersetzen, und in diesem Umfang kann man sich schnell verlieren. Es wurde beschlossen, einen zweiten Linter für die IDE zu erstellen, der ständig im Blickfeld bleibt und das Schreiben von jsdoc für Methoden und Eigenschaften, das Erstellen von Schnittstellen oder das gefürchtete onPush usw. erfordert.
Also lagen am Anfang zwei tslint-Dateien in der Wurzel.
tsconfig.json
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
200
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
false,
"ignore-params"
],
"no-duplicate-imports": true,
"no-misused-new": true,
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": false,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": [
true,
"ignore-comments",
"ignore-jsdoc"
],
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": false,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": false,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"no-output-on-prefix": false,
"no-inputs-metadata-property": true,
"no-outputs-metadata-property": true,
"no-host-metadata-property": true,
"no-input-rename": false,
"no-output-rename": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"no-consecutive-blank-lines": true
}
}tslint.ide_only.json
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"completed-docs": [
true,
{
"properties": true,
"methods": true
}
],
"no-angle-bracket-type-assertion": true,
"no-any": true,
"prefer-output-readonly": true,
"prefer-on-push-component-change-detection": true,
"array-type": [
true,
"array"
],
"typedef": [
true,
"call-signature",
"arrow-call-signature"
],
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
200
],
"member-access": [
true,
"check-parameter-property",
"check-accessor"
],
"member-ordering": [
true,
{
"order": [
"public-static-field",
"protected-static-field",
"private-static-field",
"public-instance-field",
"protected-instance-field",
"private-instance-field",
"constructor",
"public-static-method",
"protected-static-method",
"private-static-method",
"public-instance-method",
"protected-instance-method",
"private-instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": true,
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-duplicate-switch-case": true,
"no-eval": true,
"no-inferrable-types": [
false,
"ignore-params"
],
"no-duplicate-imports": true,
"one-variable-per-declaration": true,
"no-misused-new": true,
"no-non-null-assertion": true,
"prefer-template": [
true,
"allow-single-concat"
],
"ordered-imports": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": false,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": [
true,
"ignore-comments",
"ignore-jsdoc"
],
"ban": [
true,
{
"name": [
"Object",
"assign"
],
"message": "Verwenden Sie cloneDeep (lodash) zum Kopieren von Objekten"
}
],
"max-classes-per-file": [
true,
1
],
"cyclomatic-complexity": [
true,
6
],
"static-this": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": false,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"no-output-on-prefix": false,
"no-inputs-metadata-property": true,
"no-outputs-metadata-property": true,
"no-host-metadata-property": true,
"no-input-rename": false,
"no-output-rename": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"no-consecutive-blank-lines": true
}
}In der Datei src/tslint wir haben den Standard-tslint durch ide ersetzt
src/tslint.json
{
"extends": "../tslint.ide_only.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}И поправил запуск нашего линтера в скритах package.json
ng lint --tslint-config ./tslint.json --fix`Danach waren wir von den unterstrichenen Punkten frustriert, die wir korrigieren mussten.
Schritt zwei — ein paar Punkte korrigieren

Der tslint hat Regeln mit has fixer. Also lass uns das nutzen.
tslint --project tslint.ide_only.json --fix --forceHier führen wir die strengen Linter-Regeln mit automatischer Korrektur der verfügbaren Parameter aus und sorgen dafür, dass dieser Befehl keine Fehler zurückgibt (schließlich haben wir das Ziel, automatische Korrekturen vorzunehmen).
Schritt drei — schön schreiben
Wenn jeder in seinem eigenen Stil schreibt, wird das letztendlich ermüdend. Der Code sollte so geschrieben werden, dass es aussieht, als würde eine Person ihn erstellen. Dafür habe ich prettier mit den folgenden Einstellungen integriert:
.prettierr.yaml
printWidth: 200 # Maximale Anzahl der Zeichen pro Zeile
tabWidth: 2 # Anzahl der Leerzeichen im Tab
singleQuote: true # Verwendung einfacherer Anführungszeichen
trailingComma: all # Verwendung von Kommas, wo möglich
arrowParens: always # Pfeilfunktionen sehen so aus: (x) => x
overrides:
- files: "*.ts" # Überprüfung von *.ts-Dateien
options:
parser: typescript # Sprache in den *.ts-DateienUnd ich habe den Befehl hinzugefügt: prettier --write --config .prettierr.yaml
Schritt vier - Und wie befehlen Sie, das alles auszuführen?
Lassen Sie uns nun genauer untersuchen, wie wir das alles starten. Damit das funktioniert, müssen wir die folgenden Bibliotheken herunterladen:
npm i -D prettier lint-staged huskyMit husky werden wir die Ausführung unserer Befehle an den git-Hook - pre-commit - binden. lint-staged wird unsere Befehle abhängig von den geänderten Dateien ausführen (und diese Dateien ebenfalls in die Befehle einfügen).
Ich möchte auch gleich ein Problem skizzieren, mit dem ich konfrontiert war. In unserem Projekt verwenden wir ng lint. Wenn wir es zusammen mit lint-staged verwenden, fügt es uns die geänderten Dateien in den Befehl ein. ng lint hat dafür den Schlüssel --files, aber wie ich verstanden habe, erkennt es keine Gruppe von Dateien, und es muss der Schlüssel für jede Datei hinzugefügt werden. Deshalb musste ich eine Datei erstellen:
lint.sh
#!/bin/bash
PROJECT=$1
shift
SOURCES=$@
DESTINATIONS=""
DELIMITER=""
for src in $SOURCES
do
DELIMITER=" --files "
DESTINATIONS="$DESTINATIONS$DELIMITER${src}"
done
ng lint $PROJECT --tslint-config ./tslint.json $DESTINATIONSUm diese Datei zu starten, müssen wir den Projektnamen übergeben. Er befindet sich in der Datei angular.json in der Eigenschaft project. In meinem Fall ist das partner-account und partner-account-e2e. Ich benötige die 1.
Ich komme zurück zur Konfiguration. Unsere package.json sieht jetzt so aus:
"husky": {
"hooks": {
"pre-commit": "lint-staged --relative"
}
},
"lint-staged": {
"*.{ts,js}": [
"prettier --write --config .prettierr.yaml",
"tslint --project tslint.ide_only.json --fix --force",
"sh lint.sh partner-account",
"git add"
],
"*.{html,scss,css}": [
"prettier --write --config .prettierr.yaml",
"git add"
]
},Bitte beachten Sie lint-staged --relative. Parameter --relative ist erforderlich. Jetzt wird bei jedem Commit lint-stagedausgeführt. Es wählt die Dateien aus und führt je nach Auswahl die Befehle aus.
Leider hebt das die Code-Überprüfung nicht auf, aber sie ist viel sauberer geworden. Ich habe festgestellt, dass ich die Entwickler seltener an Zugriffsmodifikatoren, Methodendokumentationen und Eigenschaften erinnern muss, und ihre Kreativität wird in einem einheitlichen Stil (nun ja, fast 😀) verfasst.
P.S. — Vielen Dank für die Bilder an unseren PM.
Quelle: habr.com
