Og hej igen. Her er vi . I dag vil jeg dele en af mine hovedpine med jer, nemlig hvad man skal gøre, når et projekt skrives af mange programmerere på forskellige niveauer, med eksemplet på en Angular-applikation.

Det blev således, at jeg i lang tid kun arbejdede med mit eget team, hvor vi længe har aftalt retningslinjer for formatering, kommentering, indrykning osv. Vi blev vant til dem og levede vennerligt og lykkeligt. I glæden offentliggjorde jeg endda en artikel på Habr om . Derfor brugte vi på en eller anden magisk måde kun tslint ved pre-commit.
Og så voksede vi. Der kom et nyt projekt med arvet kode, og oven i købet nye udviklere bestående af 4 dygtige unge mænd. Og her begyndte det at gå galt.

Jeg tror, mange ved, at arbejde med arvet kode ikke er en fornøjelse. Af mine erfaringer har jeg kun fået ét projekt, som jeg var begejstret for, og resten... Så hvad var jeg nu ved at sige?) Åh ja.
Ærligt talt efterlod arkitekturen i projektet meget at ønske, og kommentarer og typer var noget, vi kun kunne drømme om. På et tidspunkt blev jeg deprimeret over, at vores dokumentation om formateringsregler ikke fungerede, kommentarer blev ikke skrevet, type — hvad er det?). Her måtte der gøres noget.
Til dem, der ikke kan vente med at kende alle trin med det samme:
Vi delte tslint op i bløde regler (til pre-commit) og strenge regler (til IDE, så den mindede om, hvad udviklerne havde glemt at gøre)
Vi satte automatisk reparation af mulige regler fra den strenge tslint på pre-commit
Vi skrev regler for prettier
Vi dansede lidt med trommerne for at få ng lint til at køre med lint-staged
Trin et — del og hersk
Da jeg fik ideen om at stramme reglerne for linteren, tænkte jeg, at vi ville hænge os selv. Koden er trods alt arvet. Man skal forstå den, og i et så stort omfang kan man blive begravet. Det blev besluttet at skabe en 2. linter til IDE, som konstant ville irritere øjnene og tvinge til at skrive jsdoc for metoder og egenskaber, skrive interfaces eller det skæbnesvangre onPush osv.
Og således lå der i roden 2 tslint-filer:
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": "Brug cloneDeep (lodash) til objektkopiering"
}
],
"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
}
}I filen src/tslint Vi har erstattet standard tslint med 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`Efter dette begyndte vi at hænge os i de fremhævede elementer, som skal rettes.
Trin to — ret et par ting

Tslint har regler med har fixer. Lad os derfor benytte os af dem.
tslint --project tslint.ide_only.json --fix --forceHer kører vi strikse linterregler med automatisk reparation af tilgængelige parametre og siger, at denne kommando ikke må returnere fejl (vores mål er trods alt at lave automatisk reparation).
Trin tre — skriv pænt
Når alle skriver i deres egen stil, bliver det i sidste ende trættende. Koden skal skrives, så det virker som om, det er lavet af én person. Til dette har jeg tilføjet prettier med følgende indstillinger:
.prettierr.yaml
printWidth: 200 # Maksimalt antal tegn pr. linje
tabWidth: 2 # Mellemrum i tabulatoren
singleQuote: true # Brug enkle anførselstegn
trailingComma: all # Brug kommaer hvor muligt
arrowParens: always # Pilfunktioner ser ud som (x) => x
overrides:
- files: "*.ts" # Tjekker filer *.ts
options:
parser: typescript # Sproget i filerne *.tsOg jeg tilføjede kommandoen: prettier --write --config .prettierr.yaml
Trin fire — Og hvordan ønsker du, at alt dette skal køres?
Lad os nu se nærmere på, hvordan alt dette skal køres. For at det skal fungere, skal vi downloade følgende biblioteker:
npm i -D prettier lint-staged huskyMed husky vil vi knytte udførelsen af vores kommandoer til git hook — pre-commit. lint-staged vil køre vores kommandoer afhængigt af de ændrede filer (og også indsætte disse filer i vores kommandoer).
Jeg vil også gerne skitsere det problem, jeg stødte på. I vores projekt bruger vi ng lint. Når vi bruger det sammen med lint-staged, tilføjes ændrede filer til vores kommando. --files, men som jeg forstod det, ser det ikke batches af filer, og det skal have denne nøgle tilføjet til hver enkelt fil. For dette måtte jeg lave en fil:
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 $DESTINATIONSFor at køre denne fil skal vi videregive projektets navn. Det findes i filen angular.json i egenskaben project. I mit tilfælde er det partner-account og partner-account-e2e. Jeg har brug for den første.
Jeg vender tilbage til indstillingen. Vores package.json ser nu således ud:
"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"
]
},Bemærk venligst lint-staged --relative. Parameter --relative det er nødvendigt. Nu kører vi ved commit lint-staged. Det vælger filer og kører en liste af kommandoer afhængig af dem.
Desværre annullerer dette ikke code review, men det er blevet meget renere. Jeg vil bemærke, at jeg sjældnere har mindet udviklerne om adgangsmodifikatorer, metodebeskrivelser og egenskaber, og deres kreativitet er nu skrevet i en ensartet stil (næsten 😀 ).
P.S. — Tak for billederne til vores PM.
Kilde: habr.com
