Und wieder hallo. Am Apparat . Heute werde ich mit Ihnen eines meiner Kopfschmerzthemen teilen, nämlich – was zu tun ist, wenn viele Programmierer unterschiedlichen Niveaus an einem Projekt arbeiten, anhand eines Beispiels einer Angular-Anwendung.

Es hat sich so ergeben, dass ich lange Zeit nur mit meinem Team gearbeitet habe, wo wir längst Regeln für das Formatieren, Kommentieren, Einrücken usw. abgestimmt hatten. Wir hatten uns daran gewöhnt und lebten glücklich und zufrieden. Aus Freude habe ich sogar einen Artikel auf Habr veröffentlicht über . Deshalb haben wir irgendwie nur TSLint beim Pre-Commit verwendet.
Und hier sind wir gewachsen. Ein neues Projekt mit geerbtem Code ist entstanden, und dazu kamen vier neue Entwickler. Und dann ging alles nicht nach Plan.

Ich denke, viele wissen, dass es kein Vergnügen ist, mit geerbtem Code zu arbeiten. Mir fällt nur ein Projekt ein, von dem ich begeistert war, und der Rest… Worüber war ich gerade?) Ach ja.
Um ehrlich zu sein, ließ die Architektur des Projekts zu wünschen übrig, und Kommentare und Typisierung waren uns nur ein Traum. Irgendwann war ich frustriert, weil unsere Dokumentation über die Formatierungsregeln nicht funktionierte, Kommentare nicht verfasst wurden, und was Typen angeht – was ist das?). Damit musste etwas gemacht werden.
Für diejenigen, die alles sofort erfahren möchten:
Wir haben TSLint in weiche Regeln (für Pre-Commit) und strenge Regeln (für die IDE, um daran zu erinnern, was die Entwickler vergessen haben) unterteilt.
Wir haben beim Pre-Commit die automatische Behebung möglicher Verstöße durch strenge TSLint-Regeln aktiviert.
Wir haben Regeln für Prettier geschrieben.
Wir haben mit einem Ritual gearbeitet, um ng lint mit lint-staged zu starten.
Schritt eins – teile und herrsche.
Als mir die Idee kam, die Regeln des Linters zu verschärfen, dachte ich, wir würden daran scheitern. Der Code ist schließlich geerbt. Man muss sich damit auseinandersetzen, und bei solchem Umfang kann man leicht ins Schleudern kommen. Es wurde beschlossen, einen zweiten Linter für die IDE zu erstellen, der die Augen beleidigt und die Entwickler zwingt, JSDoc für Methoden und Eigenschaften zu schreiben, Schnittstellen oder das gefürchtete onPush usw.
Und so lagen im Wurzelverzeichnis zwei TSLint-Dateien:
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`Nach dem haben wir angefangen, uns an den unterstrichenen Dingen, die angepasst werden müssen, festzuhalten.
Schritt zwei – ein paar Punkte anpassen

TsLint hat Regeln mit hat fixer. Lass uns das nutzen.
tslint --project tslint.ide_only.json --fix --forceHier führen wir die strengen Linter-Regeln mit der automatischen Korrektur der verfügbaren Parameter aus und sagen, dass dieser Befehl keine Fehler zurückgeben soll (schließlich ist unser Ziel die automatische Korrektur).
Schritt drei – schreibe schön
Wenn jeder in seinem eigenen Stil schreibt, wird es letztendlich ermüdend. Der Code sollte so geschrieben werden, dass es den Anschein hat, als würde ihn eine Person erstellen. Dazu habe ich Prettier mit den folgenden Einstellungen hinzugefügt:
.prettierr.yaml
printWidth: 200 # Maximale Anzahl von Zeichen in einer Zeile
tabWidth: 2 # Leerzeichen im Tab
singleQuote: true # Einfachapostrophe verwenden
trailingComma: all # Kommata wo möglich verwenden
arrowParens: always # Pfeilfunktionen sehen aus wie (x) => x
overrides:
- files: "*.ts" # Prüfung von *.ts-Dateien
options:
parser: typescript # Sprache in *.ts-DateienUnd ich habe den Befehl hinzugefügt: prettier --write --config .prettierr.yaml
Schritt vier – und wie befehlen Sie, all dies auszuführen?
Lassen Sie uns nun genauer betrachten, wie Sie all dies ausführen können. Damit dies funktioniert, müssen wir die folgenden Bibliotheken herunterladen:
npm i -D prettier lint-staged huskyMit Husky werden wir den Start unserer Befehle an den Git-Hook – pre-commit hängen. lint-staged wird unsere Befehle je nach den geänderten Dateien ausführen (diese Dateien auch in unsere Befehle einfügen).
Ich möchte sofort das Problem skizzieren, mit dem ich konfrontiert war. In unserem Projekt verwenden wir ng lint. Wenn wir es zusammen mit lint-staged verwenden, werden die geänderten Dateien zu unserem Befehl hinzugefügt. Für ng lint gibt es hierfür einen Schlüssel --files, aber wie ich verstanden habe, sieht er nicht die Sammlung von Dateien und benötigt diesen Schlüssel für jede Datei. Daher musste ich die 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 auszuführen, müssen wir den Namen des Projekts übergeben. Es befindet sich in der Datei angular.json im Attribut project. In meinem Fall ist das partner-account und partner-account-e2e. Ich benötige die erste.
Ich komme zurück zur Konfiguration. Unser 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"
]
},Achten Sie darauf, dass lint-staged --relative. Der Parameter --relative ist erforderlich. Jetzt wird beim Commit lint-staged. Es wählt die Dateien aus und führt je nach ihnen eine Liste von Befehlen aus.
Leider hebt das nicht die Code-Überprüfung auf, aber es ist viel sauberer geworden. Ich möchte anmerken, dass ich seltener die Entwickler an Zugriffsmodifizierer, Methodendeklarationen und Eigenschaften erinnern musste, und ihre Kreativität wurde in einem einheitlichen Stil (nahezu 😀 ) niedergeschrieben.
P.S. — Danke für die Bilder an unseren PM.
Quelle: habr.com
