Ako dávam veci do poriadku v projekte, kde je les priamych rúk (nastavenia tslint, krajšie atď.)

Ahoj zas. V kontakte Omelnický Sergej. Dnes sa s vami podelím o jednu z mojich bolestí hlavy, konkrétne o to, čo robiť, keď projekt píše veľa viacúrovňových programátorov na príklade aplikácie Angular.

Ako dávam veci do poriadku v projekte, kde je les priamych rúk (nastavenia tslint, krajšie atď.)

Stalo sa, že som dlho spolupracoval len so svojím tímom, kde sme mali dávno dohodnuté pravidlá formátovania, komentovania, odsadzovania atď. Zvykli si na ne a žili priateľsky a šťastne. Na oslavu som dokonca publikoval článok o Habr náš kódový štýl. Preto sme na niečo magické použili iba tslint na predbežný záväzok.

A potom sme rástli. Objavil sa nový projekt so starým kódom a okrem neho aj noví vývojári v počte 4 dobrí kolegovia. A potom veci nešli podľa plánu.

Ako dávam veci do poriadku v projekte, kde je les priamych rúk (nastavenia tslint, krajšie atď.)

Myslím, že veľa ľudí vie, že práca so starým kódom nie je zábava. V mojej pamäti som dostal len jeden projekt, z ktorého som mal radosť, a zvyšok... Tak o čom to hovorím?) Ach áno.

Úprimne povedané, architektúra v projekte zanechala veľa želaní a my sme len snívali o komentároch a písaní. V určitom okamihu som sa dostal do depresie, pretože náš dokument podľa pravidiel dizajnu nefungoval, neboli napísané komentáre, ako - čo je to?). S tým bolo treba niečo urobiť.

Pre tých, ktorí sa nevedia dočkať, kedy sa naučia všetky kroky naraz:

  • Rozdelili sme tslint na mäkké pravidlá (pre predbežný záväzok) a tvrdé pravidlá (pre ideu, aby sme vám pripomenuli, čo vývojári zabudli urobiť)

  • Pridaná automatická fixácia možných pravidiel od tvrdého tslint po predbežný záväzok

  • Napísal pravidlá pre krajšie

  • Tancovali sme s tamburínou, aby sme spustili ng lint s lint-staged

Prvý krok – rozdeľ a panuj

Keď ma napadla myšlienka sprísniť pravidlá linteru, myslel som si, že sa obesíme. Kód je dedičstvo. Treba tomu rozumieť, ale v takomto objeme sa môžete pochovať. Bolo rozhodnuté vytvoriť druhý linter pre ide, ktorý by bolel oči a nútil by vás písať jsdoc pre metódy a vlastnosti, písať rozhrania alebo nešťastný onPush atď.

Takže v koreňovom adresári sme začali mať 2 súbory 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": "Используйте cloneDeep (lodash) для копирования объекта"
      }
    ],
    "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
  }
}

V súbore src/tslint štandardný tslint sme nahradili ide

src/tslint.json

{
    "extends": "../tslint.ide_only.json",
    "rules": {
        "directive-selector": [
            true,
            "attribute",
            "app",
            "camelCase"
        ],
        "component-selector": [
            true,
            "element",
            "app",
            "kebab-case"
        ]
    }
}

A opravil som spustenie nášho linter v skriptoch package.json

ng lint --tslint-config ./tslint.json --fix`

Potom sme sa začali vešať z zdôrazňovania vecí, ktoré bolo potrebné opraviť.

Krok dva – opravte pár vecí

Ako dávam veci do poriadku v projekte, kde je les priamych rúk (nastavenia tslint, krajšie atď.)

tslint má pravidlá s has fixer. Tak to poďme využiť.

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

Tu spustíme pravidlá tvrdého linteru s automatickou opravou dostupných parametrov a povieme tomuto príkazu, aby nevracal chyby (naším cieľom je predsa vykonať automatickú opravu).

Tretí krok - píšte krásne

Keď každý píše po svojom, časom to omrzí. Kód musí byť napísaný tak, aby sa zdalo, že to robí jedna osoba. Aby som to urobil, nainštaloval som krajšie s nasledujúcimi nastaveniami:

.prettierr.yaml

printWidth: 200     # Максимальное кол-во символов в строке
tabWidth: 2         # Пробелов в Табе
singleQuote: true   # Использовать одинарные кавычки
trailingComma: all  # Использовать запятые где возможно
arrowParens: always # Стрелочные ф-ии выглядят (x) => x
overrides:
  - files: "*.ts"   # Проверка файлов *.ts
    options:
      parser: typescript  # Язык в файлах *.ts

A pridal príkaz: prettier --write --config .prettierr.yaml

Štvrtý krok – Ako to všetko spustíte?

Poďme sa teraz bližšie pozrieť na to, ako to všetko spustiť. Aby to fungovalo, musíme si stiahnuť nasledujúce knižnice:

npm i -D prettier lint-staged husky

Pomocou husky zavesíme spustenie našich príkazov na git hook - pre-commit. lint-staged za nás spustí príkazy v závislosti od zmenených súborov (tiež tieto súbory nahraďte našimi príkazmi).

Hneď by som chcel načrtnúť problém, s ktorým som sa stretol. V našom projekte používame ng lint. Keď ho použijeme v spojení s lint-staged, upravené súbory sa pridajú do nášho príkazu. ng lint má na to kľúč --files, ale ako som pochopil, nevidí veľa súborov a tento kľúč potrebuje pridať do každého súboru. Aby som to urobil, musel som vytvoriť súbor:

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

Na spustenie tohto súboru musíme zadať názov projektu. Je to v súbore angular.json v majetku projektu. V mojom prípade je partner-account и partner-account-e2e. Potrebujem ten 1.

Vráťme sa k nastaveniam. Náš súbor package.json teraz vyzerá takto:

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

Venujte pozornosť lint-staged --relative. Parameter --relative tam požadované. Teraz, keď sa zaviažeme, začne to lint-staged. Ten zase vyberie súbory a spustí zoznam príkazov v závislosti od nich.

Žiaľ, kontrola kódu sa tým nezruší, ale stala sa oveľa čistejšou. Podotýkam, že som vývojárom začal menej často pripomínať modifikátory prístupu, popisy metód a vlastností a ich práca sa začala písať rovnakým štýlom (no, skoro 😀).

PS - Ďakujeme za fotky nášmu PM.

Zdroj: hab.com

Kúpte si spoľahlivý hosting pre stránky s DDoS ochranou, VPS VDS servery 🔥 Kúpte si spoľahlivý webhosting s ochranou DDoS, VPS VDS servery | ProHoster