¡Hola de nuevo! Aquí está . Hoy compartiré uno de mis dolores de cabeza, es decir, qué hacer cuando un proyecto es desarrollado por muchos programadores de diferentes niveles, usando un ejemplo de una aplicación Angular.

Resulta que durante mucho tiempo trabajé solo con mi equipo, donde ya teníamos establecidas las reglas de formateo, comentarios, sangrías, etc. Nos adaptamos a ellas y vivimos felices y en paz. De hecho, tan emocionado estaba que publiqué un artículo en Habr sobre . Así que, de alguna manera mágica, solo utilizamos tslint en el pre-commit.
Y aquí crecimos. Apareció un nuevo proyecto con código heredado, y junto a él nuevos desarrolladores, un total de cuatro buenos jóvenes. Y aquí las cosas no salieron como se planeó.

Creo que muchos saben que trabajar con código heredado no es divertido. En mi experiencia, solo he recibido un proyecto del que estuve realmente entusiasmado, y el resto... ¿De qué estaba hablando? Ah sí.
Hablando francamente, la arquitectura del proyecto dejaba mucho que desear, y los comentarios y la tipificación eran solo un sueño. En algún momento me desanimé al darme cuenta de que nuestra documentación sobre las reglas de formateo no funcionaba, no se escribían comentarios, ¿tipificación qué es?). Teníamos que hacer algo al respecto.
Para aquellos que no pueden esperar a conocer todos los pasos de inmediato:
Dividimos tslint en reglas suaves (para pre-commit) y reglas duras (para IDE, para recordar a los desarrolladores lo que olvidaron hacer)
Implementamos la auto-corrección de posibles reglas por parte del tslint duro en el pre-commit
Escribimos reglas para prettier
Realizamos malabares para ejecutar ng lint con lint-staged
Primer paso: divide y vencerás
Cuando se me ocurrió la idea de endurecer las reglas del linter, pensé que nos colgaríamos. El código es heredado. Hay que entenderlo, y con tal volumen te puedes enterrar en él. Se decidió crear un segundo linter para la IDE, que molestara a la vista y obligara a escribir jsdoc para métodos y propiedades, a crear interfaces o el infame onPush, etc.
Así que en la raíz teníamos dos archivos 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": "Utilice cloneDeep (lodash) para copiar el objeto"
}
],
"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
}
}En el archivo src/tslint hemos reemplazado el tslint estándar por 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`Después de lo cual comenzamos a tener problemas con los elementos subrayados que necesitan ser corregidos.
Paso dos — corregir algunos puntos

El tslint tiene reglas con tiene arreglador. Así que aprovechemos.
tslint --project tslint.ide_only.json --fix --forceAquí estamos ejecutando las reglas estrictas del linter con ajustes automáticos de los parámetros disponibles y estamos indicando que este comando no devuelva errores (pues nuestro objetivo es hacer la corrección automática).
Paso tres — escribe bonito
Cuando cada uno escribe a su manera, al final cansa. El código debe escribirse de tal manera que parezca que lo hace una sola persona. Por esto, he añadido prettier, con las siguientes configuraciones:
.prettierr.yaml
printWidth: 200 # Número máximo de caracteres por línea
tabWidth: 2 # Espacios en la tabulación
singleQuote: true # Usar comillas simples
trailingComma: all # Usar comas donde sea posible
arrowParens: always # Las funciones de flecha lucen (x) => x
overrides:
- files: "*.ts" # Comprobación de archivos *.ts
options:
parser: typescript # Lenguaje en archivos *.tsY añadí el comando: prettier --write --config .prettierr.yaml
Paso cuatro — ¿Y cómo se supone que ejecutarás todo esto?
Ahora analicemos más de cerca cómo ejecutar todo esto. Para que todo esto funcione necesitamos descargar las siguientes bibliotecas:
npm i -D prettier lint-staged huskyCon husky, colocaremos la ejecución de nuestros comandos en el gancho de git — pre-commit. lint-staged ejecutará nuestros comandos dependiendo de los archivos modificados (también pasará esos archivos en nuestros comandos).
También me gustaría delinear el problema con el que yo me encontré. En nuestro proyecto utilizamos ng lint. Cuando lo usamos en combinación con lint-staged, los archivos modificados se agregan a nuestro comando. ng lint tiene una clave para esto --files, pero, como entendí, no ve un conjunto de archivos, y necesita que este clave se añada a cada archivo. Para esto, tuve que crear un archivo:
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 ejecutar este archivo debemos pasar el nombre del proyecto. Se encuentra en el archivo angular.json en la propiedad project. En mi caso es partner-account y partner-account-e2e. Necesito el primero.
Volveré a la configuración. Nuestro package.json ahora se ve así:
"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"
]
},Presta atención a lint-staged --relative. El parámetro --relative es obligatorio. Ahora, al hacer commit, se ejecuta lint-staged. Este, a su vez, selecciona archivos y ejecuta una lista de comandos dependiendo de ellos.
Desafortunadamente, esto no elimina la revisión de código, pero ha mejorado mucho. Notaré que ahora recuerdo menos a los desarrolladores sobre modificadores de acceso, descripciones de métodos y propiedades, y su creatividad está escrita en un estilo unificado (bueno, casi 😀 ).
P.S. — Gracias por las imágenes a nuestro PM.
Fuente: habr.com
