Hogyan hoztam rendbe egy projektet, ahol a kéz direkt, mint az erdő (tslint, prettier, stb.)

Üdvözlök mindenkit újra. Itt van Omelnyickij Szergej. Ma megosztom Önökkel az egyik fejfájásomat, nevezetesen, mit kell tenni, amikor sok különböző szintű programozó ír egy projektet, például egy angular alkalmazás esetében.

Hogyan hoztam rendbe egy projektet, ahol a kéz direkt, mint az erdő (tslint, prettier, stb.)

Ez így alakult, hogy hosszú ideig csak a saját csapatommal dolgoztam, ahol már régóta megegyeztünk a megjelenítési, kommentálási és behúzási szabályokban, stb. Hozzászoktunk ehhez, és boldogan éltünk. Az örömömben még írtam egy cikket a Habr-ra a kódstílusunkról. Ezért valami varázslatos okból csak a tslint-t használtuk pre-commit előtt.

És itt egyszer csak megnőttünk. Megjelent egy új projekt örökölt kóddal, és ehhez négy új fejlesztő is csatlakozott. És itt valami nem ment a tervek szerint.

Hogyan hoztam rendbe egy projektet, ahol a kéz direkt, mint az erdő (tslint, prettier, stb.)

Sokan tudják, hogy az örökölt kóddal való munka nem egy élvezet. Amennyire emlékszem, csak egy projekt volt, amire fel voltam dobva, a többi… Szóval miről is beszélek? Ja, igen.

Őszintén szólva a projekt architektúrája hagyott kívánnivalót maga után, és a kommentárok és a típusok, nos, azokat csak álmodtuk. Egy pillanatban elkeseredtem attól, hogy a dokumentációnk a bemutatási szabályokról nem működik, nem írják a kommentárokat, a típus - mi is ez?). Ezzel valamit kezdeni kellett.

Aki alig várja, hogy az összes lépést azonnal tudja:

  • A tslint-et lágy és kemény szabályokra osztottuk (pre-commit-ra) és (ide-ra, hogy emlékeztesse a fejlesztőket, hogy mit felejtettek el csinálni)

  • Az előzetes kötelező auto-fixálásokat alkalmaztuk a kemény tslint szabályaiból

  • Szabályokat írtunk a prettier-hez

  • Táncoltunk a bűbájjal, hogy elindítsuk az ng lint-et a lint-staged-dal

Első lépés - oszd meg és uralkodj

Amikor eszembe jutott a linterszabályok szigorítása, azt gondoltam, hogy meg fogunk őrülni. A kód örökölt volt. Ezt meg kell érteni, és ekkora terjedelemben el lehet veszni. Döntés született, hogy egy második lintert készítünk az ide-nek, amely folyamatosan figyelmeztetne a szemem előtt, és rávenne, hogy jsdoc-ot írjak a módszerekhez és attribútumokhoz, interfészeket vagy a balszerencsés onPush-t stb.

Tehát a gyökérben két tslint fájl kezdete volt:

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": "Használj cloneDeep-et (lodash) az objektum másolásához"
      }
    ],
    "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
  }
}

A fájlban src/tslint A szokásos tslint-t a ide-re cseréltük.

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`

Ezek után a kiemelt dolgoktól kezdünk el akadozni, amelyeket ki kell javítani.

Második lépés — néhány dolgot ki kell javítani

Hogyan hoztam rendbe egy projektet, ahol a kéz direkt, mint az erdő (tslint, prettier, stb.)

A tslint-nek vannak szabályai a has fixer. Tehát használjuk őket.

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

Itt futtatjuk a szigorú linter szabályait, automatikusan kijavítva a lehetséges paramétereket, és azt mondjuk, hogy ez a parancs ne adjon vissza hibákat (a célunk végül is a hibák automatikus javítása).

Harmadik lépés — írj szépen

Amikor mindenki a saját stílusában ír, az végül is fárasztó. A kódot úgy kell írni, hogy úgy érezzük, mintha egy ember csinálná. Ehhez csatlakoztattam a prettiert az alábbi beállításokkal:

.prettierr.yaml

printWidth: 200     # Maximális karakterek száma egy sorban
tabWidth: 2         # Tabok száma
singleQuote: true   # Használjunk egyszeres idézőjeleket
trailingComma: all  # Használjunk vesszőket, ahol lehetséges
arrowParens: always # A nyíl f-ák így néznek ki: (x) => x
overrides:
  - files: "*.ts"   # Ellenőrzés *.ts fájloknál
    options:
      parser: typescript  # A nyelv a *.ts fájlokban

És hozzáadtam a parancsot: prettier --write --config .prettierr.yaml

Negyedik lépés — És hogyan parancsolod, hogy mindezt futtassuk?

Nézzük most részletesebben, hogyan futtassuk mindezt. Ahhoz, hogy minden jól működjön, le kell töltenünk a következő könyvtárakat:

npm i -D prettier lint-staged husky

A husky segítségével a parancsainkat a git hurokra felfüggesztjük — pre-commit. A lint-staged a parancsainkat az módosított fájlok alapján futtatja (alapján behelyettesíti ezeket a fájlokat a parancsainkba).

Szeretném még azonnal felvázolni a problémát, amivel szembesültem. A projektünkben használjuk az ng lint-et. Amikor ezt a lint-staged-del kombinálva használjuk, akkor a parancsunkhoz hozzáadódnak a módosított fájlok. Az ng lint-nek van erre egy kulcsa --files, de ahogy megértettem, nem látja a fájlok halmazát, és minden fájlhoz hozzá kell adni ezt a kulcsot. Ezért létre kellett hoznom egy fájlt:

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

Ennek a fájlnak a futtatásához meg kell adnunk a projekt nevét. Ez a fájlban található. angular.json a project tulajdonságban. Az én esetemben ez partner-account és partner-account-e2e. Az elsőra van szükségem.

Visszatérek a beállításhoz. A package.json fájlunk most így néz ki:

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

Kérjük, vegye figyelembe lint-staged --relative. A paraméter --relative ez kötelező. Most, amikor elkötelezünk, elindítjuk a lint-staged. Ez pedig kiválasztja a fájlokat, és attól függően futtatja a parancsok listáját.

Sajnos ez nem zárja ki a kódellenőrzést, de sokkal tisztábbá vált. Megjegyzem, hogy ritkábban emlékeztetem a fejlesztőket a hozzáférési módosítókra, a metódusok és tulajdonságok leírására, és az ő kreativitásuk egy egységes stílusban íródott (nos, szinte 😀 ).

P.S. — Köszönet a képekért a PM-nek.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster