Hoe ik orde schepte in een project waar de boel een rommel was (instellingen tslint, prettier, etc.)

Hallo weer. Hier is Sergio Omelnytskyy. Vandaag deel ik een van mijn hoofdpijnen met jullie, namelijk wat je moet doen wanneer een project geschreven wordt door veel verschillende programmeurs, aan de hand van een Angular-applicatie.

Hoe ik orde schepte in een project waar de boel een rommel was (instellingen tslint, prettier, etc.)

Het gebeurde zo dat ik lange tijd alleen met mijn team werkte, waar we al lang de regels voor codestijl, commentaar, inspringingen enz. hadden afgestemd. We waren eraan gewend en leefden gelukkig samen. Van blijdschap publiceerde ik zelfs een artikel op Habr over onze codestijl. Daarom gebruikten we op magische wijze alleen tslint voor pre-commit.

En toen groeide we. Er kwam een nieuw project met geërfde code, en daarmee kwamen ook nieuwe ontwikkelaars, vier in getal, erbij. En toen ging het allemaal niet volgens plan.

Hoe ik orde schepte in een project waar de boel een rommel was (instellingen tslint, prettier, etc.)

Ik denk dat velen weten dat werken met geërfde code geen pretje is. In herinnering heb ik slechts één project dat me echt gelukkig maakte, de rest... Dus waar was ik? Ah ja.

Eerlijk gezegd liet de architectuur in het project veel te wensen over, en opmerkingen en type-aanduidingen waren slechts een droom. Op een gegeven moment raakte ik ontmoedigd omdat onze documentatie over de coderegels niet werkte, opmerkingen niet werden geschreven, type - wat is dat?). Hier moest echt iets aan gedaan worden.

Voor degenen die niet kunnen wachten om meteen alle stappen te leren:

  • We hebben tslint verdeeld in zachte regels (voor pre-commit) en strikte regels (voor ide, zodat het herinnerde aan wat ontwikkelaars vergaten te doen)

  • We hebben automatische correctie van mogelijke regels van de strikte tslint op pre-commit gezet

  • We hebben regels voor prettier geschreven

  • We hebben gedanst met een shaman om ng lint met lint-staged te laten draaien

Stap één - scheiden en heersen

Toen ik het idee kreeg om de regels van de linter te verstrakken, dacht ik dat we onszelf de das omdoen. De code is immers geërfd. Je moet erdoorheen werken, en in zo'n hoeveelheid kun je je verliezen. Er werd besloten om een tweede linter voor de ide te maken, die het zicht zou aantasten en zou dwingen om jsdoc voor methoden en eigenschappen te schrijven, interfaces of de beruchte onPush enz.

Dus in de root hebben we twee tslint-bestanden liggen:

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": "Gebruik cloneDeep (lodash) voor het kopiëren van een object"
      }
    ],
    "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
  }
}

In het bestand src/tslint wij hebben de standaard tslint vervangen door 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`

Daarna werden we overweldigd door de onderstreepte dingen die moesten worden gecorrigeerd.

Stap twee — een paar dingen corrigeren

Hoe ik orde schepte in een project waar de boel een rommel was (instellingen tslint, prettier, etc.)

De tslint heeft regels met heeft fixer. Laten we het gebruiken.

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

Hier voeren we de strikte linterregels uit met automatische correctie van beschikbare parameters en vertellen we dat deze opdracht geen fouten moet retourneren (ons doel is tenslotte automatische correctie te doen).

Stap drie — schrijf mooi

Wanneer iedereen in zijn eigen stijl schrijft, is dat uiteindelijk vermoeiend. Code moet zo worden geschreven dat het lijkt alsof één persoon het schrijft. Daarom heb ik prettier toegevoegd, met de volgende instellingen:

.prettierr.yaml

printWidth: 200     # Maximale aantal tekens per regel
tabWidth: 2         # Spaties per tab
singleQuote: true   # Gebruik enkele aanhalingstekens
trailingComma: all  # Gebruik komma's waar mogelijk
arrowParens: always # Pijlfuncties zien eruit als (x) => x
overrides:
  - files: "*.ts"   # Controleer bestanden *.ts
    options:
      parser: typescript  # Taal in bestanden *.ts

En ik heb de opdracht toegevoegd: prettier --write --config .prettierr.yaml

Stap vier — En hoe moet je dit allemaal uitvoeren?

Laten we nu dieper ingaan op hoe we dit allemaal kunnen uitvoeren. Om dit te laten werken, moeten we de volgende bibliotheken downloaden:

npm i -D prettier lint-staged husky

Met husky koppelen we de uitvoering van onze opdrachten aan de git hook — pre-commit. lint-staged zal de opdrachten uitvoeren afhankelijk van de gewijzigde bestanden (en deze bestanden ook aan onze opdrachten toevoegen).

Ik wil ook meteen het probleem schetsen waarmee ik werd geconfronteerd. In ons project gebruiken we ng lint. Wanneer we het in combinatie met lint-staged gebruiken, worden onze gewijzigde bestanden aan de opdracht toegevoegd. --files, maar zoals ik begreep, ziet hij de batch bestanden niet en moet deze sleutel aan elk bestand worden toegevoegd. Hiervoor moest ik een bestand maken:

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

Om dit bestand uit te voeren, moeten we de naam van het project doorgeven. Deze staat in het bestand angular.json in de eigenschap project. In mijn geval is dat partner-account en partner-account-e2e. Ik heb de eerste nodig.

Ik keer terug naar de configuratie. Onze package.json ziet er nu zo uit:

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

Let op de lint-staged --relative. De parameter --relative is verplicht. Nu wordt er bij een commit automatisch lint-staged. Het selecteert bestanden en voert op basis daarvan een lijst met commando's uit.

Helaas vervangt dit de code-review niet, maar het is veel netter geworden. Ik merk dat ik ontwikkelaars minder vaak hoef te herinneren aan toegangmodifiers, beschrijvingen van methodes en eigenschappen, en hun creativiteit is nu in een consistente stijl geschreven (bijna 😀 ).

P.S. — Bedankt voor de afbeeldingen aan onze PM.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster