Pozdrav opet. Ostanimo u kontaktu. Danas ću s vama podijeliti jednu od svojih glavobolja: što učiniti kada projekt uključuje više programera različitih razina vještina, koristeći primjer Angular aplikacije.

Slučajno sam dugo radio isključivo sa svojim timom, gdje smo se odavno dogovorili o pravilima formatiranja, komentiranja, uvlačenja i tako dalje. Navikli smo se na njih i živjeli sretno do kraja života. U svojoj radosti, čak sam objavio i članak na Habru. Dakle, od svega magičnog, tslint smo koristili samo na pre-commitu.
A onda smo rasli. Pojavio se novi projekt sa starim kodom, zajedno s četiri nova programera. A onda stvari nisu išle po planu.

Mislim da mnogi znaju da rad sa starim kodom nije zabavan. Sjećam se samo jednog projekta s kojim sam bio oduševljen, a ostali... Pa o čemu sam pričao?) Oh da.
Iskreno, arhitektura projekta je ostavljala mnogo toga za poželjeti, a komentari i tipovi sustava bili su daleki san. U nekom trenutku me obeshrabrila činjenica da naša dokumentacija o pravilima formatiranja nije radila, komentari se nisu pisali, a tipovi sustava - što je to bilo?). Nešto se moralo poduzeti po tom pitanju.
Za one koji jedva čekaju naučiti sve korake odjednom:
Podijelili smo tslint na meka pravila (za pre-commit) i tvrda pravila (za IDE, kako bismo podsjetili programere što su zaboravili učiniti).
Dodano automatsko ispravljanje mogućih pravila prije potvrde iz hard tslinta
Napisao/la sam pravila za ljepše
Plesali smo uz tamburin kako bismo lansirali ng lint s lint-insceniranim
Prvi korak: podijeli pa vladaj
Kad sam došao na ideju da pooštrim pravila za linter, mislio sam da ćemo propasti. Kod je naslijeđen. Morate ga razumjeti, a s toliko koda možete se izgubiti. Donjela se odluka o stvaranju drugog lintera za IDE, onog koji bi bio ruglo i prisilio vas da pišete jsdoc za metode i svojstva, pišete sučelja ili nesretni onPush i tako dalje.
Dakle, u korijenu smo počeli imati 2 tslin datoteke:
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": "Используйте cloneDeep (lodash) для копирования объекта"
}
],
"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
}
}U spisu src/tslint Standardni tslint zamijenili smo s ide
src/clint.json
{
"extends": "../tslint.ide_only.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}I popravio sam pokretanje našeg lintera u skriptama package.json.
ng lint --tslint-config ./tslint.json --fix`Nakon čega smo se počeli vješati o podcrtane stvari koje je trebalo ispraviti.
Drugi korak - popravite nekoliko stvari

tslint ima pravila s has fixerPa iskoristimo ga.
tslint --project tslint.ide_only.json --fix --forceOvdje pokrećemo hard linter pravila s automatskim ispravljanjem dostupnih parametara i kažemo ovoj naredbi da ne vraća greške (naš cilj ovdje je izvršiti automatsko ispravljanje).
Treći korak: pišite lijepo
Kad svatko piše u svom stilu, to s vremenom postane zamorno. Kod treba pisati na način koji ostavlja dojam kao da ga piše jedna osoba. Da bih to postigao, koristio sam Prettier sa sljedećim postavkama:
.prettierr.yaml
printWidth: 200 # Максимальное кол-во символов в строке
tabWidth: 2 # Пробелов в Табе
singleQuote: true # Использовать одинарные кавычки
trailingComma: all # Использовать запятые где возможно
arrowParens: always # Стрелочные ф-ии выглядят (x) => x
overrides:
- files: "*.ts" # Проверка файлов *.ts
options:
parser: typescript # Язык в файлах *.tsI dodao je naredbu: prettier --write --config .prettierr.yaml
Četvrti korak: A kako namjeravate sve ovo pokrenuti?
Sada pogledajmo pobliže kako sve ovo pokrenuti. Da bi ovo funkcioniralo, moramo preuzeti sljedeće biblioteke:
npm i -D prettier lint-staged huskyKoristeći husky, povezat ćemo naše naredbe s git hook-om—pre-commit. lint-staged će izvršavati naredbe za nas na temelju promijenjenih datoteka (i također će umetnuti te datoteke u naše naredbe).
Želio bih odmah opisati problem na koji sam naišao. U našem projektu koristimo ng lint. Kada ga koristimo zajedno s lint-staged, modificirane datoteke se dodaju našoj naredbi. ng lint ima prekidač za to. --files, ali koliko ja razumijem, ne može vidjeti hrpu datoteka i svakoj datoteci treba dodati ovaj ključ. Da bih to učinio, morao sam stvoriti datoteku:
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 $DESTINATIONSZa pokretanje ove datoteke, moramo proslijediti naziv projekta. Nalazi se u datoteci angular.json u svojstvu projekta. U mom slučaju, to je partner-account и partner-account-e2eTrebam prvi.
Vratimo se na postavljanje. Naš package.json sada izgleda ovako:
"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"
]
},Napomena lint-staged --relative. Parametar --relative je tamo potreban. Sada, kada izvršimo commit, on se izvršava lint-stagedOn, pak, odabire datoteke i pokreće popis naredbi ovisno o njima.
Nažalost, ovo ne eliminira pregled koda, ali je postao puno čišći. Napominjem da sam postao manje sklon podsjećati programere na modifikatore pristupa, deklaracije metoda i svojstva, a njihov rad je postao dosljedan (pa, gotovo 😀).
PS - Hvala našoj privatnoj poruci za slike.
Izvor: www.habr.com
