E mais uma vez olá. Aqui é . Hoje vou compartilhar com você uma das minhas dores de cabeça, ou seja — o que fazer quando um projeto é desenvolvido por muitos programadores de diferentes níveis no exemplo de uma aplicação Angular.

Acontece que, durante muito tempo, trabalhei apenas com minha equipe, onde há muito tempo já tínhamos alinhado as regras de formatação, comentários, recuos e assim por diante. Nos acostumamos com elas e vivemos de forma harmoniosa e feliz. Na empolgação, até publiquei um artigo no Habr sobre . Portanto, por algo mágico, usávamos apenas TSLint no pré-commit.
E então crescemos. Surgiu um novo projeto com código herdado, e, para piorar, novos desenvolvedores, no total de 4 homens bons. E aqui as coisas começaram a sair do planejado.

Acho que muitos sabem que trabalhar com código herdado não é uma alegria. Na minha memória, recebi apenas um projeto do qual realmente gostei, e o resto... Então, sobre o que eu estava falando?) Ah, sim.
Francamente, a arquitetura do projeto deixava a desejar, e os comentários e a tipagem eram apenas um sonho nosso. Em determinado momento, desanimei ao perceber que nossa documentação sobre as regras de formatação não funcionava, os comentários não eram escritos, tipagem — o que é isso?). Precisávamos fazer algo a respeito.
Para aqueles que não conseguem esperar para saber todos os passos de uma vez:
Dividimos o TSLint em regras suaves (para pré-commit) e regras rígidas (para IDE, para lembrar os desenvolvedores do que esqueceram de fazer)
Colocamos um sistema de auto-correção para as possíveis regras do TSLint rígido no pré-commit
Escrevemos regras para Prettier
Dançamos ao redor do fogo para rodar o ng lint com lint-staged
Passo um — divida e conquistará
Quando tive a ideia de tornar as regras do linter mais rígidas, pensei que iríamos nos enforcar. O código é herdado. Precisa ser compreendido, e em tal volume, podemos nos enterrar. A decisão foi tomada de criar um segundo linter para a IDE, que irritasse os olhos e obrigasse a escrever JSDoc para métodos e propriedades, criar interfaces ou o infeliz onPush, e assim por diante.
Portanto, na raiz, temos dois arquivos TSLint:
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": "Use cloneDeep (lodash) to copy the 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
}
}No arquivo src/tslint substituímos o tslint padrão pelo 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`Depois disso, começamos a nos deparar com elementos sublinhados que precisavam ser corrigidos.
Passo dois — corrigir alguns pontos

O tslint possui regras com tem corretor. Então vamos aproveitar.
tslint --project tslint.ide_only.json --fix --forceAqui estamos acionando as regras do linter rigoroso com a auto-correção de parâmetros disponíveis e dizendo para que esse comando não retorne erros (aqui nosso objetivo é fazer correções automáticas).
Passo três — escreva bonito
Quando cada um escreve à sua maneira, isso acaba se tornando cansativo. O código deve ser escrito de forma a parecer que está sendo feito por uma única pessoa. Para isso, anexei o prettier com as seguintes configurações:
.prettierr.yaml
printWidth: 200 # Número máximo de caracteres na linha
tabWidth: 2 # Espaços no tab
singleQuote: true # Usar aspas simples
trailingComma: all # Usar vírgulas onde possível
arrowParens: always # Funções de seta são exibidas como (x) => x
overrides:
- files: "*.ts" # Verificação de arquivos *.ts
options:
parser: typescript # Linguagem nos arquivos *.tsE adicionei o comando: prettier --write --config .prettierr.yaml
Passo quatro — E como você vai executar tudo isso?
Vamos agora detalhar como executar tudo isso. Para que tudo funcione, precisamos baixar as seguintes bibliotecas:
npm i -D prettier lint-staged huskyCom o husky, vamos acionar a execução de nossos comandos em um hook do git — pre-commit. O lint-staged irá executar nossos comandos com base nos arquivos alterados (também inserindo esses arquivos em nossos comandos).
Gostaria também de descrever imediatamente o problema que enfrentei. No nosso projeto, usamos ng lint. Quando o usamos em conjunto com o lint-staged, os arquivos alterados se adicionam ao nosso comando. O ng lint tem uma opção para isso --files, mas, como eu entendi, ele não reconhece um grupo de arquivos e precisa que essa opção seja adicionada para cada arquivo. Por isso, precisei criar um arquivo:
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 $DESTINATIONSPara executar esse arquivo, precisamos passar o nome do projeto. Ele está no arquivo angular.json na propriedade project. No meu caso, é partner-account e partner-account-e2e. Eu preciso do primeiro.
Voltando à configuração. Nosso package.json agora está assim:
"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"
]
},Preste atenção em lint-staged --relative. Parâmetro --relative é obrigatório. Agora, ao fazer um commit, estamos executando lint-staged. Este, por sua vez, seleciona os arquivos e executa, dependendo deles, a lista de comandos.
Infelizmente, isso não elimina a revisão de código, mas agora ela é muito mais limpa. Vale ressaltar que eu raramente preciso lembrar os desenvolvedores sobre modificadores de acesso, a descrição de métodos e propriedades, e a criatividade deles passou a ser escrita em um estilo único (quase 😃).
P.S. — Agradeço pelas imagens ao nosso PM.
Fonte: habr.com
