E ancora buongiorno. Qui è . Oggi condividerò con te una delle mie questioni più complicate, ovvero cosa fare quando un progetto è sviluppato da molti programmatori di diversi livelli, prendendo come esempio un'applicazione Angular.

Per lungo tempo ho lavorato solo con il mio team, dove avevamo già concordato da tempo le regole di formattazione, commento, rientri, ecc. Ci siamo abituati a queste pratiche e abbiamo lavorato bene e felici insieme. Per celebrare, ho persino pubblicato un articolo su Habr riguardo al . Pertanto, in modo magico, usavamo solo tslint nei pre-commit.
E qui siamo cresciuti. È emerso un nuovo progetto con codice ereditato, e ad esso si sono uniti nuovi sviluppatori, per un totale di 4 abili ragazzi. E qualcosa è andato storto.

Credo che molti sappiano che lavorare con codice ereditato non è piacevole. Nella mia esperienza, ho ricevuto solo un progetto che mi ha entusiasmato, e per il resto... Di cosa stavo parlando? Ah già.
A dire dire la verità, l'architettura nel progetto lasciava molto a desiderare, e commenti e tipizzazione sembravano solo un sogno. A un certo punto, mi sono sentito demoralizzato dal fatto che la nostra documentazione sulle regole di formattazione non funzionava, i commenti non venivano scritti, e il tipo - che cos'è?). Su questo bisognava fare qualcosa.
Per coloro che non vedono l'ora di conoscere tutti i passaggi subito:
Abbiamo diviso tslint in regole soft (per pre-commit) e regole hard (per ide, per ricordare ai programmatori ciò che hanno dimenticato di fare)
Abbiamo implementato la correzione automatica delle possibili regole da tslint rigido su pre-commit
Abbiamo scritto regole per prettier
Abbiamo danzato per avviare ng lint con lint-staged
Passo uno - dividi e conquista
Quando mi è venuta l'idea di irrigidire le regole del linter, ho pensato che ci saremmo impiccati. Il codice è ereditato. Bisogna capirlo, e in un tale volume si può affondare. Si è deciso di creare un secondo linter per l'ide, che non darebbe pace e costringerebbe a scrivere jsdoc per i metodi e le proprietà, scrivere interfacce o il maledetto onPush, ecc.
Quindi, nella radice, avevamo due file 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": "Utilizza cloneDeep (lodash) per copiare un oggetto"
}
],
"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
}
}Nel file src/tslint abbiamo sostituito il tslint standard con 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`Dopo di che abbiamo cominciato a riscontrare problemi con gli elementi sottolineati che necessitano di correzione.
Passo due — correzione di alcuni aspetti

tslint ha regole con has fixer. Quindi sfruttiamo questa opportunità.
tslint --project tslint.ide_only.json --fix --forceQui avviamo le regole del linter rigoroso con la correzione automatica delle impostazioni disponibili e diciamo che questo comando non deve restituire errori (dopotutto, il nostro obiettivo è fare correzioni automatiche).
Passo tre — scrivi in modo elegante
Quando ognuno scrive nel proprio stile, alla fine risulta stancante. È fondamentale scrivere il codice in modo da sembrare prodotto da una sola persona. Per questo ho aggiunto prettier, con le seguenti impostazioni:
.prettierr.yaml
printWidth: 200 # Numero massimo di caratteri per riga
tabWidth: 2 # Spazi per tabulazione
singleQuote: true # Utilizzare le virgolette singole
trailingComma: all # Utilizzare le virgole dove possibile
arrowParens: always # Le funzioni freccia appaiono come (x) => x
overrides:
- files: "*.ts" # Controllo dei file *.ts
options:
parser: typescript # Linguaggio nei file *.tsE ho aggiunto il comando: prettier --write --config .prettierr.yaml
Passo quattro — E come ci ordini di eseguire tutto questo?
Adesso analizziamo più dettagliatamente come eseguire tutto ciò. Per far funzionare il tutto, dobbiamo scaricare le seguenti librerie:
npm i -D prettier lint-staged huskyUtilizzando husky, collegheremo l'esecuzione dei nostri comandi a un hook git — pre-commit. lint-staged eseguirà i comandi in base ai file modificati (includendo anche questi file nei nostri comandi).
Vorrei anche delineare il problema con cui mi sono imbattuto. Nel nostro progetto utilizziamo ng lint. Quando lo usiamo insieme a lint-staged, i file modificati vengono aggiunti al nostro comando. ng lint ha una chiave per questo --files, ma, come ho capito, non vede un gruppo di file e ha bisogno di aggiungere questa chiave per ogni file. Per questo ho creato un file:
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 $DESTINATIONSPer avviare questo file, dobbiamo fornire il nome del progetto. Si trova nel file angular.json nella proprietà project. Nel mio caso, è partner-account e partner-account-e2e. Ho bisogno del primo.
Tornerò alla configurazione. Il nostro package.json ora appare così:
"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"
]
},Fai attenzione a lint-staged --relative. Il parametro --relative è obbligatorio. Ora, al momento del commit, viene eseguito lint-staged. Questo seleziona i file e avvia la lista di comandi in base a essi.
Purtroppo, non elimina la revisione del codice, ma è diventata molto più pulita. Nota che ora ricordo meno frequentemente agli sviluppatori riguardo ai modificatori di accesso, alla descrizione dei metodi e delle proprietà, e la loro creatività è scritta in uno stile uniforme (beh, quasi 😀 ).
P.S. — Grazie per le immagini al nostro PM.
Fonte: habr.com
