Kako sem uredil projekt, kjer je bilo vse narejeno s prostimi rokami (nastavitve tslint, prettier, itd.)

Pozdravljeni še enkrat. Tu je Sergej Omelnicki. Danes bom z vami delil eno svojih največjih skrbi, in sicer - kaj storiti, ko projekt pišejo številni programerji različnih nivojev, na primeru aplikacije Angular.

Kako sem uredil projekt, kjer je bilo vse narejeno s prostimi rokami (nastavitve tslint, prettier, itd.)

Tako se je zgodilo, da sem dolgo časa delal le s svojo ekipo, kjer smo že zdavnaj uskladili pravila oblikovanja, komentiranja, zamude itd. Navadili smo se jih in živeli srečno in zadovoljno. Na veselje sem celo objavil članek na Habr o našem slogovnem vodniku. Zato smo iz nekega čarobnega razloga uporabljali samo tslint pri pre-commit.

In potem smo se razširili. Pojavil se je nov projekt z dediščinjenim kodo, v dodatku pa novi razvijalci v številu 4 dobrih fantov. In tukaj se je vse začelo zapletati.

Kako sem uredil projekt, kjer je bilo vse narejeno s prostimi rokami (nastavitve tslint, prettier, itd.)

Mislim, da mnogi vedo, da delo z dediščinjenim kodeksom ni ravno prijetno. V mojem spominu sem prejel le en projekt, od katerega sem bil navdušen, vse ostalo ... O čem sem govoril? Ah, ja.

Odkrito povedano, arhitektura v projektu je zapustila veliko željo, komentarji in tipizacija so nam bile le pobožne želje. V nekem trenutku sem obupano gledal, saj naša dokumentacija o pravilih oblikovanja ne deluje, komentarji se ne pišejo, tip - kaj je to?). S tem je bilo potrebno nekaj narediti.

Za tiste, ki komaj čakajo, da izvejo vse korake takoj:

  • Razdelili smo tslint na mehka pravila (za pre-commit) in stroga pravila (za ide, da spominja, da so razvijalci pozabili narediti)

  • Dali smo na pre-commit avto-fiksacijo možnih pravil iz strogega tslint

  • Napisali smo pravila za prettier

  • Plesali smo s bobnom, da zaženemo ng lint s lint-staged

Prvi korak — razdeli in obvladaj

Ko mi je prišla ideja o zaostrovanju pravil lintra, sem pomislil, da se bomo obesili. Koda je dedična. Razviti jo je potrebno, a v takem obsegu se lahko zakoplješ. Odločeno je bilo ustvariti 2. linter za ide, ki bi pokal oči in silil pisati jsdoc za metode in lastnosti, pisati vmesnike ali tragična onPush itd.

Torej imamo v korenu 2 tslin datoteki:

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": "Uporabite cloneDeep (lodash) za kopiranje objekta"
      }
    ],
    "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 datoteki src/tslint Zamenjali smo standardni tslint z 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`

Po potrebi smo se začeli obremenjevati s podčrtanimi stvarmi, ki jih je treba popraviti.

Drugi korak — popraviti nekaj stvari

Kako sem uredil projekt, kjer je bilo vse narejeno s prostimi rokami (nastavitve tslint, prettier, itd.)

Z tslint imamo pravila s ima samodejni popravek. Torej izkoristimo.

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

Tu zaženemo stroga pravila linterja s samodejnim popravljanjem dostopnih parametrov in povemo, da ta ukaz ne vrne napak (naš cilj je le samodejno popraviti).

Tretji korak — piši lepo

Ko vsak piše na svoj način, to na koncu utrudi. Kodo je treba pisati tako, da izgleda, kot da jo piše ena oseba. Za to sem dodal prettier z naslednjimi nastavitvami:

.prettierr.yaml

printWidth: 200     # Maksimalno število znakov v vrstici
tabWidth: 2         # Število presledkov v Tabu
singleQuote: true   # Uporabi enojne narekovaje
trailingComma: all  # Uporabi vejice, kjer je mogoče
arrowParens: always # Puščice funkcije izgledajo (x) => x
overrides:
  - files: "*.ts"   # Preverba datotek *.ts
    options:
      parser: typescript  # Jezik v datotekah *.ts

In dodal sem ukaz: prettier --write --config .prettierr.yaml

Četrti korak — Kako boš ukazoval vse to zagnati?

Poglejmo zdaj podrobneje, kako vse to zagnati. Da to vse deluje, moramo prenesti naslednje knjižnice:

npm i -D prettier lint-staged husky

S husky bomo povezali izvajanje naših ukazov na git hook — pre-commit. lint-staged bo izvajal naše ukaze v odvisnosti od spremenjenih datotek (prav tako vključeval te datoteke v naše ukaze).

Želim takoj izpostaviti težavo, s katero sem se srečal. V našem projektu uporabljamo ng lint. Ko ga uporabljamo v povezavi z lint-staged, se našemu ukazu dodajo spremenjene datoteke. ng lint ima za to ključno besedo --files, toda, kot sem razumel, ne vidi paketa datotek in mu je treba za vsako datoteko dodati to ključno besedo. Zato sem moral ustvariti datoteko:

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

Za zagon te datoteke moramo posredovati ime projekta. To je v datoteki angular.json v lastnosti project. V mojem primeru je to partner-account in partner-account-e2e. Potrebujem 1.

Vrnil se bom k nastavitvam. Naš package.json zdaj izgleda tako:

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

Upoštevajte lint-staged --relative. Parameter --relative je nu obvezen. Zdaj ob trdnem zavezi se nam zažene lint-staged. Ta izbira datoteke izvaja seznam ukazov glede na te datoteke.

Žal to ne odpravlja pregleda kode, a postal je veliko bolj čistejši. Opazil sem, da sem redkeje opominjal razvijalce glede dostopovnih modifierjev, opisa metod in lastnosti, njihov izraz pa je bolj enotno napisan (no skoraj 😀 ).

P.S. — Hvala za slike našemu PM.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster