Hallo weer. Hier is . Vandaag deel ik een van mijn hoofdpijnen met jullie, namelijk wat je moet doen wanneer een project geschreven wordt door veel verschillende programmeurs, aan de hand van een Angular-applicatie.

Het gebeurde zo dat ik lange tijd alleen met mijn team werkte, waar we al lang de regels voor codestijl, commentaar, inspringingen enz. hadden afgestemd. We waren eraan gewend en leefden gelukkig samen. Van blijdschap publiceerde ik zelfs een artikel op Habr over . Daarom gebruikten we op magische wijze alleen tslint voor pre-commit.
En toen groeide we. Er kwam een nieuw project met geërfde code, en daarmee kwamen ook nieuwe ontwikkelaars, vier in getal, erbij. En toen ging het allemaal niet volgens plan.

Ik denk dat velen weten dat werken met geërfde code geen pretje is. In herinnering heb ik slechts één project dat me echt gelukkig maakte, de rest... Dus waar was ik? Ah ja.
Eerlijk gezegd liet de architectuur in het project veel te wensen over, en opmerkingen en type-aanduidingen waren slechts een droom. Op een gegeven moment raakte ik ontmoedigd omdat onze documentatie over de coderegels niet werkte, opmerkingen niet werden geschreven, type - wat is dat?). Hier moest echt iets aan gedaan worden.
Voor degenen die niet kunnen wachten om meteen alle stappen te leren:
We hebben tslint verdeeld in zachte regels (voor pre-commit) en strikte regels (voor ide, zodat het herinnerde aan wat ontwikkelaars vergaten te doen)
We hebben automatische correctie van mogelijke regels van de strikte tslint op pre-commit gezet
We hebben regels voor prettier geschreven
We hebben gedanst met een shaman om ng lint met lint-staged te laten draaien
Stap één - scheiden en heersen
Toen ik het idee kreeg om de regels van de linter te verstrakken, dacht ik dat we onszelf de das omdoen. De code is immers geërfd. Je moet erdoorheen werken, en in zo'n hoeveelheid kun je je verliezen. Er werd besloten om een tweede linter voor de ide te maken, die het zicht zou aantasten en zou dwingen om jsdoc voor methoden en eigenschappen te schrijven, interfaces of de beruchte onPush enz.
Dus in de root hebben we twee tslint-bestanden liggen:
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": "Gebruik cloneDeep (lodash) voor het kopiëren van een object"
}
],
"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 het bestand src/tslint wij hebben de standaard tslint vervangen door 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`Daarna werden we overweldigd door de onderstreepte dingen die moesten worden gecorrigeerd.
Stap twee — een paar dingen corrigeren

De tslint heeft regels met heeft fixer. Laten we het gebruiken.
tslint --project tslint.ide_only.json --fix --forceHier voeren we de strikte linterregels uit met automatische correctie van beschikbare parameters en vertellen we dat deze opdracht geen fouten moet retourneren (ons doel is tenslotte automatische correctie te doen).
Stap drie — schrijf mooi
Wanneer iedereen in zijn eigen stijl schrijft, is dat uiteindelijk vermoeiend. Code moet zo worden geschreven dat het lijkt alsof één persoon het schrijft. Daarom heb ik prettier toegevoegd, met de volgende instellingen:
.prettierr.yaml
printWidth: 200 # Maximale aantal tekens per regel
tabWidth: 2 # Spaties per tab
singleQuote: true # Gebruik enkele aanhalingstekens
trailingComma: all # Gebruik komma's waar mogelijk
arrowParens: always # Pijlfuncties zien eruit als (x) => x
overrides:
- files: "*.ts" # Controleer bestanden *.ts
options:
parser: typescript # Taal in bestanden *.tsEn ik heb de opdracht toegevoegd: prettier --write --config .prettierr.yaml
Stap vier — En hoe moet je dit allemaal uitvoeren?
Laten we nu dieper ingaan op hoe we dit allemaal kunnen uitvoeren. Om dit te laten werken, moeten we de volgende bibliotheken downloaden:
npm i -D prettier lint-staged huskyMet husky koppelen we de uitvoering van onze opdrachten aan de git hook — pre-commit. lint-staged zal de opdrachten uitvoeren afhankelijk van de gewijzigde bestanden (en deze bestanden ook aan onze opdrachten toevoegen).
Ik wil ook meteen het probleem schetsen waarmee ik werd geconfronteerd. In ons project gebruiken we ng lint. Wanneer we het in combinatie met lint-staged gebruiken, worden onze gewijzigde bestanden aan de opdracht toegevoegd. --files, maar zoals ik begreep, ziet hij de batch bestanden niet en moet deze sleutel aan elk bestand worden toegevoegd. Hiervoor moest ik een bestand maken:
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 $DESTINATIONSOm dit bestand uit te voeren, moeten we de naam van het project doorgeven. Deze staat in het bestand angular.json in de eigenschap project. In mijn geval is dat partner-account en partner-account-e2e. Ik heb de eerste nodig.
Ik keer terug naar de configuratie. Onze package.json ziet er nu zo uit:
"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"
]
},Let op de lint-staged --relative. De parameter --relative is verplicht. Nu wordt er bij een commit automatisch lint-staged. Het selecteert bestanden en voert op basis daarvan een lijst met commando's uit.
Helaas vervangt dit de code-review niet, maar het is veel netter geworden. Ik merk dat ik ontwikkelaars minder vaak hoef te herinneren aan toegangmodifiers, beschrijvingen van methodes en eigenschappen, en hun creativiteit is nu in een consistente stijl geschreven (bijna 😀 ).
P.S. — Bedankt voor de afbeeldingen aan onze PM.
Bron: habr.com
