Come ho sistemato il progetto dove c'era una giungla di codici (impostazioni tslint, prettier, ecc.)

E di nuovo, buongiorno. Qui è Omel'nyts'kyi Serhiy. Oggi condividerò con voi uno dei miei grattacapi, ovvero cosa fare quando un progetto è sviluppato da molti programmatori di diversi livelli, prendendo ad esempio un'applicazione Angular.

Come ho sistemato il progetto dove c'era una giungla di codici (impostazioni tslint, prettier, ecc.)

Si è sempre pensato che ho lavorato a lungo solo con il mio team, dove avevamo già da tempo concordato le regole di formattazione, commento, rientri, ecc. Ci siamo adattati a queste regole e abbiamo vissuto in armonia e felicità. Nella gioia, ho persino pubblicato un articolo su Habr riguardo a il nostro style guide. Quindi, per qualche motivazione magica, abbiamo utilizzato solo tslint nei pre-commit.

E qui siamo cresciuti. È emerso un nuovo progetto con codice ereditato, e a quello si sono aggiunti quattro nuovi sviluppatori. Ecco che le cose hanno cominciato ad andare male.

Come ho sistemato il progetto dove c'era una giungla di codici (impostazioni tslint, prettier, ecc.)

Penso che molti sappiano che lavorare con codice ereditato non sia piacevole. Nella mia esperienza, ho ricevuto solo un progetto che mi ha entusiasta, per il resto... Di cosa stavo parlando?) Ah già.

A dire il vero, l'architettura del progetto lasciava a desiderare, e i commenti e la tipizzazione erano solo un sogno. A un certo punto, mi sono sentito giù perché la nostra documentazione sulle regole di formattazione non funzionava, i commenti non venivano scritti, e la tipizzazione — che cos'è?). Su questo bisognava fare qualcosa.

Per coloro che non vedono l'ora di scoprire subito tutti i passi:

  • Abbiamo suddiviso tslint in regole morbide (per pre-commit) e regole rigide (per ide, in modo da ricordare ai programmatori le cose dimenticate)

  • Abbiamo attivato l'auto-correzione per le possibili regole di tslint rigido nei pre-commit

  • Abbiamo scritto regole per prettier

  • Abbiamo danzato con uno sciamano per far partire ng lint con lint-staged

Primo passo — dividi e conquista

Quando mi è venuta l'idea di inasprire le regole del linter, ho pensato che ci saremmo impiccati. Il codice è ereditato. Bisogna entrare nel merito, e in una tale quantità si può sprofondare. È stata presa la decisione di creare un secondo linter per ide, che tenesse d'occhio e costringesse a scrivere jsdoc per metodi e proprietà, scrivere interfacce o il famigerato onPush, e così via.

Quindi, alla radice abbiamo iniziato con due file tslint:

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": "Utilizzare 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 iniziato a preoccuparci delle cose sottolineate che devono essere corrette.

Fase due — correggere un paio di punti

Come ho sistemato il progetto dove c'era una giungla di codici (impostazioni tslint, prettier, ecc.)

Il tslint ha regole con has fixer. Quindi facciamo uso di questo.

tslint --project tslint.ide_only.json --fix --force

Qui stiamo eseguendo le regole del linter rigoroso con l'auto-fix per i parametri disponibili e diciamo affinché questo comando non restituisca errori (qui il nostro obiettivo è comunque fare correzioni automatiche).

Fase tre — scrivere in modo elegante

Quando ognuno scrive nel proprio stile, alla fine diventa stancante. Il codice deve essere scritto in modo che sembri fatto da una sola persona. Per questo ho integrato prettier, con le seguenti impostazioni:

.prettierr.yaml

printWidth: 200     # Numero massimo di caratteri in una riga
tabWidth: 2         # Spazi in tab
singleQuote: true   # Utilizza apici singoli
trailingComma: all  # Utilizza virgole dove possibile
arrowParens: always # Le funzioni freccia appaiono (x) => x
overrides:
  - files: "*.ts"   # Controlla i file *.ts
    options:
      parser: typescript  # Linguaggio nei file *.ts

E ho aggiunto il comando: prettier --write --config .prettierr.yaml

Fase quattro — E come ordini di eseguire tutto questo?

Diamo ora un'occhiata più da vicino a come eseguire tutto questo. Affinché funzioni, dobbiamo scaricare le seguenti librerie:

npm i -D prettier lint-staged husky

Con husky collegheremo l'esecuzione dei nostri comandi a un hook git — pre-commit. lint-staged eseguirà i comandi in base ai file modificati (includendo anch'essi nei comandi).

Vorrei anche descrivere subito il problema con cui mi sono trovato. Nel nostro progetto utilizziamo ng lint. Quando lo utilizziamo 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 insieme di file, e ha bisogno di aggiungere questa chiave per ogni file. Per questo ho dovuto creare 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 $DESTINATIONS

Per eseguire questo file dobbiamo passare il nome del progetto. Questo si trova nel file angular.json nella proprietà project. Nel mio caso è partner-account e partner-account-e2e. Ho bisogno del primo.

Tornando 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"
    ]
  },

Prestare attenzione a lint-staged --relative. Il parametro --relative è obbligatorio. Ora, durante il commit, viene eseguito lint-staged. Esso seleziona i file e avvia, a seconda di essi, un elenco di comandi.

Sfortunatamente, questo non annulla la revisione del codice, ma è diventata molto più pulita. Nota che ricordo meno frequentemente ai programmatori riguardo ai modificatori di accesso, alla descrizione dei metodi e delle proprietà, e la loro creatività è scritta in uno stile uniforme (quasi 😀 ).

P.S. — Grazie per le immagini al nostro PM.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster