Hvordan jeg fik styr på et projekt med et væld af direkte indgreb (konfiguration af tslint, prettier osv.)

Og hej igen. Her er vi Omelnytskyi Serhii. I dag vil jeg dele en af mine hovedpine med jer, nemlig hvad man skal gøre, når et projekt skrives af mange programmerere på forskellige niveauer, med eksemplet på en Angular-applikation.

Hvordan jeg fik styr på et projekt med et væld af direkte indgreb (konfiguration af tslint, prettier osv.)

Det blev således, at jeg i lang tid kun arbejdede med mit eget team, hvor vi længe har aftalt retningslinjer for formatering, kommentering, indrykning osv. Vi blev vant til dem og levede vennerligt og lykkeligt. I glæden offentliggjorde jeg endda en artikel på Habr om vores kodestil. Derfor brugte vi på en eller anden magisk måde kun tslint ved pre-commit.

Og så voksede vi. Der kom et nyt projekt med arvet kode, og oven i købet nye udviklere bestående af 4 dygtige unge mænd. Og her begyndte det at gå galt.

Hvordan jeg fik styr på et projekt med et væld af direkte indgreb (konfiguration af tslint, prettier osv.)

Jeg tror, mange ved, at arbejde med arvet kode ikke er en fornøjelse. Af mine erfaringer har jeg kun fået ét projekt, som jeg var begejstret for, og resten... Så hvad var jeg nu ved at sige?) Åh ja.

Ærligt talt efterlod arkitekturen i projektet meget at ønske, og kommentarer og typer var noget, vi kun kunne drømme om. På et tidspunkt blev jeg deprimeret over, at vores dokumentation om formateringsregler ikke fungerede, kommentarer blev ikke skrevet, type — hvad er det?). Her måtte der gøres noget.

Til dem, der ikke kan vente med at kende alle trin med det samme:

  • Vi delte tslint op i bløde regler (til pre-commit) og strenge regler (til IDE, så den mindede om, hvad udviklerne havde glemt at gøre)

  • Vi satte automatisk reparation af mulige regler fra den strenge tslint på pre-commit

  • Vi skrev regler for prettier

  • Vi dansede lidt med trommerne for at få ng lint til at køre med lint-staged

Trin et — del og hersk

Da jeg fik ideen om at stramme reglerne for linteren, tænkte jeg, at vi ville hænge os selv. Koden er trods alt arvet. Man skal forstå den, og i et så stort omfang kan man blive begravet. Det blev besluttet at skabe en 2. linter til IDE, som konstant ville irritere øjnene og tvinge til at skrive jsdoc for metoder og egenskaber, skrive interfaces eller det skæbnesvangre onPush osv.

Og således lå der i roden 2 tslint-filer:

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": "Brug cloneDeep (lodash) til objektkopiering"
      }
    ],
    "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
  }
}

I filen src/tslint Vi har erstattet standard tslint med 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`

Efter dette begyndte vi at hænge os i de fremhævede elementer, som skal rettes.

Trin to — ret et par ting

Hvordan jeg fik styr på et projekt med et væld af direkte indgreb (konfiguration af tslint, prettier osv.)

Tslint har regler med har fixer. Lad os derfor benytte os af dem.

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

Her kører vi strikse linterregler med automatisk reparation af tilgængelige parametre og siger, at denne kommando ikke må returnere fejl (vores mål er trods alt at lave automatisk reparation).

Trin tre — skriv pænt

Når alle skriver i deres egen stil, bliver det i sidste ende trættende. Koden skal skrives, så det virker som om, det er lavet af én person. Til dette har jeg tilføjet prettier med følgende indstillinger:

.prettierr.yaml

printWidth: 200     # Maksimalt antal tegn pr. linje
tabWidth: 2         # Mellemrum i tabulatoren
singleQuote: true   # Brug enkle anførselstegn
trailingComma: all  # Brug kommaer hvor muligt
arrowParens: always # Pilfunktioner ser ud som (x) => x
overrides:
  - files: "*.ts"   # Tjekker filer *.ts
    options:
      parser: typescript  # Sproget i filerne *.ts

Og jeg tilføjede kommandoen: prettier --write --config .prettierr.yaml

Trin fire — Og hvordan ønsker du, at alt dette skal køres?

Lad os nu se nærmere på, hvordan alt dette skal køres. For at det skal fungere, skal vi downloade følgende biblioteker:

npm i -D prettier lint-staged husky

Med husky vil vi knytte udførelsen af vores kommandoer til git hook — pre-commit. lint-staged vil køre vores kommandoer afhængigt af de ændrede filer (og også indsætte disse filer i vores kommandoer).

Jeg vil også gerne skitsere det problem, jeg stødte på. I vores projekt bruger vi ng lint. Når vi bruger det sammen med lint-staged, tilføjes ændrede filer til vores kommando. --files, men som jeg forstod det, ser det ikke batches af filer, og det skal have denne nøgle tilføjet til hver enkelt fil. For dette måtte jeg lave en fil:

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

For at køre denne fil skal vi videregive projektets navn. Det findes i filen angular.json i egenskaben project. I mit tilfælde er det partner-account og partner-account-e2e. Jeg har brug for den første.

Jeg vender tilbage til indstillingen. Vores package.json ser nu således ud:

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

Bemærk venligst lint-staged --relative. Parameter --relative det er nødvendigt. Nu kører vi ved commit lint-staged. Det vælger filer og kører en liste af kommandoer afhængig af dem.

Desværre annullerer dette ikke code review, men det er blevet meget renere. Jeg vil bemærke, at jeg sjældnere har mindet udviklerne om adgangsmodifikatorer, metodebeskrivelser og egenskaber, og deres kreativitet er nu skrevet i en ensartet stil (næsten 😀 ).

P.S. — Tak for billederne til vores PM.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster