Bună din nou. La voi! . Astăzi voi împărtăși cu voi una dintre durerile mele de cap, și anume — ce să faci când la un proiect scriu mulți programatori de diferite niveluri, folosind un exemplu de aplicație Angular.

Așa s-a întâmplat, că am lucrat mult timp doar cu echipa mea, unde de mult timp stabilisem reguli de formatare, comentare, indentare etc. Ne-am obișnuit cu ele și am trăit împreună fericiți. Din fericire, am publicat chiar un articol pe Habr despre . De aceea, dintr-o dată magică, am folosit doar tslint la pre-commit.
Și aici ne-am extins. A apărut un nou proiect cu un cod moștenit, iar la el s-au alăturat 4 dezvoltatori buni. Și atunci ceva a mers prost.

Cred că mulți știu că lucrul cu codul moștenit nu este plăcut. Din câte îmi amintesc, am primit doar un singur proiect de care am fost încântat, iar restul... Despre ce vorbeam?) Ah da.
Sincer să fiu, arhitectura din proiect lăsa de dorit, iar comentariile și tipizarea ne visau doar. La un moment dat, m-am descurajat de faptul că documentația noastră despre regulile de formatare nu funcționează, comentariile nu se scriu, tip — ce este asta?). Aici trebuia să facem ceva.
Pentru cei care nu mai pot de nerăbdare să afle toate etapele dintr-o dată:
Am împărțit tslint în reguli blânde (pentru pre-commit) și reguli stricte (pentru ide, pentru a-i aminti dezvoltatorului că a uitat să facă ceva)
Am atașat la pre-commit auto-fixarea regulilor posibile de la tslint strict
Am scris reguli pentru prettier
Am țopăit cu tamburul pentru a porni ng lint cu lint-staged
Pasul întâi — împărțite și stăpânește
Când mi-a venit ideea de a strica regulile linternelor, m-am gândit că ne vom spânzura. Codul este moștenit. Trebuie să ne descurcăm, iar la o asemenea amploare ne putem îngropa. S-a decis să creăm un al doilea linter pentru ide, care să ne bată la ochi și să ne forțeze să scriem jsdoc pentru metode și proprietăți, să scriem interfețe sau blestematul onPush etc.
Așadar, în rădăcină, am avut 2 fișiere tslin:
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": "Folosiți cloneDeep (lodash) pentru a copia un obiect"
}
],
"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
}
}În fișierul src/tslint am înlocuit tslint-ul standard cu ide
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`După aceea, am început să ne mișcăm din cauza elementelor subliniate care trebuiau corectate.
Pasul doi — corectează câteva aspecte

tslint are reguli pentru are fixator. Așa că să ne folosim.
tslint --project tslint.ide_only.json --fix --forceAici lansăm regulile stricte ale linterului cu corectarea automată a parametrilor disponibili și spunem ca această comandă să nu returneze erori (deoarece scopul nostru este, totuși, de a face corectări automate).
Pasul trei — scrie frumos
Când fiecare scrie în stilul său, devine în cele din urmă obositor. Codul trebuie scris astfel încât să pară că este realizat de o singură persoană. Pentru asta, am adăugat prettier, cu următoarele setări:
.prettierr.yaml
printWidth: 200 # Numărul maxim de caractere pe linie
tabWidth: 2 # Spații în tab
singleQuote: true # Folosește ghilimele simple
trailingComma: all # Folosește virgule unde este posibil
arrowParens: always # Funcțiile lambda arată (x) => x
overrides:
- files: "*.ts" # Verificarea fișierelor *.ts
options:
parser: typescript # Limbajul din fișierele *.tsȘi am adăugat comanda: prettier --write --config .prettierr.yaml
Pasul patru — Cum îmi comanzi să rulez tot asta?
Hai să detaliem acum cum să rulăm totul. Pentru ca totul să funcționeze, trebuie să descărcăm următoarele librării:
npm i -D prettier lint-staged huskyFolosind husky, vom lega executarea comenzilor noastre de hook-ul git — pre-commit. lint-staged va rula comenzile în funcție de fișierele modificate (de asemenea, va încadra aceste fișiere în comenzile noastre).
Aș dori să conturez imediat o problemă cu care m-am confruntat. În proiectul nostru folosim ng lint. Când îl folosim împreună cu lint-staged, fișierele modificate sunt adăugate la comanda noastră. ng lint are o cheie pentru asta --files, dar, așa cum am înțeles, nu vede un grup de fișiere, și trebuie să adaug această cheie pentru fiecare fișier în parte. Din acest motiv, a trebuit să creez un fișier:
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 $DESTINATIONSPentru a rula acest fișier, trebuie să transmitem numele proiectului. Acesta se află în fișier angular.json în proprietatea project. În cazul meu, acesta este partner-account și partner-account-e2e. Am nevoie de primul.
Revin la setare. Acum package.json-ul nostru arată astfel:
"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"
]
},Rețineți că lint-staged --relative. Parametrul --relative este obligatoriu. Acum, atunci când facem un commit, se lansează lint-staged. Acesta, la rândul său, selectează fișierele și rulează o listă de comenzi în funcție de acestea.
Din păcate, acest lucru nu anulează revizuirea codului, dar a devenit mult mai curat. Observ că le-am reamintit mai rar dezvoltatorilor despre modificatorii de acces, descrierea metodelor și proprietăților, iar creativitatea lor a fost scrisă într-un stil uniform (aproape 😀 ).
P.S. — Mulțumesc pentru imagini echipei noastre.
Sursa: habr.com
