Comment j'ai mis de l'ordre dans un projet où c'était la jungle (paramètres tslint, prettier, etc.)

Bonjour à nouveau. Ici, c'est Sergueï Omelnytskyi. Aujourd'hui, je vais partager avec vous l'une de mes préoccupations, à savoir que faire lorsque plusieurs programmeurs de différents niveaux travaillent sur un projet, comme dans le cas d'une application Angular.

Comment j'ai mis de l'ordre dans un projet où c'était la jungle (paramètres tslint, prettier, etc.)

Il se trouve que, pendant longtemps, je n'ai travaillé qu'avec ma propre équipe, où nous avions depuis longtemps convenu des règles de mise en forme, de commentaires, d'indentation, etc. Nous nous y étions habitués et vivions en harmonie et en bonheur. Dans la joie, j'ai même publié un article sur Habr au sujet de notre style de code. Par conséquent, pour une raison mystérieuse, nous ne passionnions que l'utilisation de TSLint lors des pré-commits.

Et puis nous avons grandi. Un nouveau projet a vu le jour avec un code hérité, et de nouveaux développeurs se sont joints à nous, quatre jeunes gens bien sympathiques. Et là, cela n'est pas allé comme prévu.

Comment j'ai mis de l'ordre dans un projet où c'était la jungle (paramètres tslint, prettier, etc.)

Je pense que beaucoup savent que travailler avec du code hérité n'est pas agréable. Dans ma mémoire, je n'ai reçu qu'un seul projet qui m'a enthousiasmé, tout le reste... Donc, de quoi parlais-je ? Ah oui.

Pour être franc, l'architecture du projet laissait à désirer, et les commentaires et la typage étaient comme un rêve inaccessible. À un moment donné, j'ai été désillusionné par le fait que notre documentation sur les règles de mise en forme ne fonctionnait pas, que les commentaires n'étaient pas écrits, et que la typologie — qu'est-ce que c'est ?). Il fallait faire quelque chose à ce sujet.

Pour ceux qui ne peuvent pas attendre de connaître tous les étapes d'un seul coup :

  • Nous avons divisé TSLint en règles souples (pour pré-commit) et règles strictes (pour IDE, pour rappeler aux développeurs ce qu'ils ont oublié de faire)

  • Nous avons ajouté une auto-correction pour les règles que TSLint stricte a pu détecter pendant le pré-commit

  • Nous avons écrit des règles pour Prettier

  • Nous avons dansé avec le tambour chamanique pour lancer ng lint avec lint-staged

Première étape — diviser et régner

Lorsque l'idée de durcir les règles du linter m'est venue, j'ai pensé que nous allions nous pendre. Le code étant hérité. Il faut s'y plonger, et avec un tel volume, on peut s'enliser. Il a été décidé de créer un deuxième linter pour l'IDE, qui irait gratter les yeux et obligerait à rédiger jsdoc pour les méthodes et les propriétés, à écrire des interfaces ou le redoutable onPush, etc.

Ainsi, à la racine, nous avions deux fichiers 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": "Utilisez cloneDeep (lodash) pour copier un objet"
      }
    ],
    "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
  }
}

Dans le fichier src/tslint Nous avons remplacé le tslint standard par 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`

Après cela, nous avons commencé à être surpris par les éléments soulignés qui nécessitent des corrections.

Étape deux — corriger quelques points

Comment j'ai mis de l'ordre dans un projet où c'était la jungle (paramètres tslint, prettier, etc.)

Le tslint a des règles avec un correcteur. Alors profitons-en.

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

Ici, nous exécutons les règles d'un linter strict avec correction automatique des paramètres disponibles et nous disons à cette commande de ne pas retourner d'erreurs (c'est notre objectif d'effectuer des corrections automatiques).

Étape trois — écrivez proprement

Quand chacun écrit à sa manière, cela finit par devenir fatiguant. Le code doit être écrit de manière à ce qu'on ait l'impression qu'une seule personne le rédige. Pour cela, j'ai ajouté prettier, avec les paramètres suivants :

.prettierr.yaml

printWidth: 200     # Nombre maximum de caractères par ligne
tabWidth: 2         # Espaces dans une tabulation
singleQuote: true   # Utiliser des guillemets simples
trailingComma: all  # Utiliser des virgules là où c'est possible
arrowParens: always # Les fonctions fléchées apparaissent sous la forme (x) => x
overrides:
  - files: "*.ts"   # Vérification des fichiers *.ts
    options:
      parser: typescript  # Langue dans les fichiers *.ts

Et j'ai ajouté la commande : prettier --write --config .prettierr.yaml

Étape quatre — Comment vas-tu exécuter tout cela ?

Examinons maintenant en détail comment exécuter tout cela. Pour que tout cela fonctionne, nous devons télécharger les bibliothèques suivantes :

npm i -D prettier lint-staged husky

Avec husky, nous allons accrocher l'exécution de nos commandes sur le hook git — pre-commit. lint-staged exécutera nos commandes en fonction des fichiers modifiés (et les fournira à nos commandes).

J'aimerais également évoquer le problème auquel j'ai été confronté. Dans notre projet, nous utilisons ng lint. Lorsque nous l'utilisons en conjonction avec lint-staged, les fichiers modifiés sont ajoutés à notre commande. ng lint a une clé pour cela --files, mais, à ce que j'ai compris, il ne voit pas un groupe de fichiers et il faut ajouter cette clé pour chaque fichier. Pour cela, j'ai dû créer un fichier :

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

Pour exécuter ce fichier, nous devons passer le nom du projet. Il se trouve dans le fichier angular.json dans la propriété project. Dans mon cas, il s'agit de partner-account et partner-account-e2e. J'ai besoin du premier.

Je reviens à la configuration. Notre package.json ressemble maintenant à ceci :

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

Veuillez noter que lint-staged --relative. Le paramètre --relative est requis. Maintenant, lors du commit, nous exécutons lint-staged. Cela sélectionne les fichiers et exécute une liste de commandes en fonction de ceux-ci.

Malheureusement, cela n'annule pas la révision de code, mais cela est devenu beaucoup plus propre. Je remarque que je rappelle moins souvent aux développeurs de se souvenir des modificateurs d'accès, de la description des méthodes et des propriétés, et leur créativité est désormais écrite dans un style uniforme ( enfin presque 😀 ).

P.S. — Merci pour les images à notre PM.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster