Hello again. In touch Omelnitsky Sergey . Today I will share with you one of my headaches, namely, what to do when a lot of multilevel programmers write a project using the example of an angular application.
It so happened that for a long time I worked only with my team, where we had long agreed on the rules for formatting, commenting, indentation, etc. I got used to them and lived together happily. To celebrate, I even published an article on Habr on our code style . Therefore, from something magical, we used only tslint on the pre-commit.
And then we grew. A new project with an inherited code appeared, and to it, in addition to new developers, in the amount of 4 good fellows. And even here it didn’t go according to plan.
I think many people know that working with legacy code is not high. In my memory, I received only one project from which I was delighted, and the rest ... So what am I talking about?) Oh yes.
Frankly, the architecture in the project left much to be desired, and we only dreamed of commenting and typing. At some point, I was saddened by the fact that our document didn’t work according to the design rules, comments were not written, type - what is this?). Here it was necessary to do something with this.
We divided tslint into soft rules (for pre-commit) and hard rules (for ide, so that it reminds us of what the developers forgot to do)
Hang on pre-commit autofixation of possible rules from hard tslint
Wrote rules for prettier
Danced with a tambourine to run ng lint with lint-staged
When I got the idea to tighten the rules of the linter, I thought that we would hang ourselves. The code is inherited. You need to understand it, and in such a volume you can dig. It was decided to create a 2nd linter for ide, which would call my eyes and force me to write jsdoc for methods and sv, write interfaces or unhappy onPush, etc.
So, in the root, we started to lie 2 tslin files:
{ "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 } }
{ "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": " cloneDeep (lodash) " } ], "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 the src/tslint
we replaced the standard tslint with ide
{ "extends": "../tslint.ide_only.json", "rules": { "directive-selector": [ true, "attribute", "app", "camelCase" ], "component-selector": [ true, "element", "app", "kebab-case" ] } }
And fixed the launch of our linter in the scripts of package.json
ng lint --tslint-config ./tslint.json --fix`
Then we began to hang ourselves from the underlined things that need to be corrected.
Tslint has rules with has fixer
. So let's use it.
tslint --project tslint.ide_only.json --fix --force
Here we run the rules of the hard linter with autofixation of the available parameters and say that this command does not return errors (here our goal is still to do auto-correction).
When everyone writes in his own way, it ultimately tires. The code needs to be written so that it seems that this is done by one person. For this, I screwed prettier, with the following settings:
printWidth: 200 # - tabWidth: 2 # singleQuote: true # trailingComma: all # arrowParens: always # - (x) => x overrides: - files: "*.ts" # *.ts options: parser: typescript # *.ts
And he added the command: prettier --write --config .prettierr.yaml
Let's now take a closer look at how to start it all. In order for this to work, we need to download the following:
npm i -D prettier lint-staged husky
With husky, we will hang the launch of our commands on a git hook - pre-commit. lint-staged will run commands for us depending on the changed files (also substitute these files for us in commands).
I would also like to immediately outline the problem that I encountered. In our project we use ng lint. When we use it in conjunction with lint-staged, modified files are added to our command. Ng lint has the --files
key for this, but as I understand it, it does not see a bunch of files, and it needs to add this key to each file. To do this, I had to create a file:
#!/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
To run this file, we must pass the name of the project. It is located in the angular.json
file in the project property. In my case, this is partner-account
and partner-account-e2e
. I need a 1st.
Back to the setup. Our package.json now looks like this:
"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" ] },
Check out lint-staged --relative
. The --relative
parameter is --relative
there. Now when we commit, lint-staged
launched. He, in turn, selects files and starts a list of commands depending on them.
Unfortunately this does not cancel the code review, but it has become much cleaner. I note that I less often began to remind developers about access modifiers, a description of methods and sv, and their work began to be written in the same style (well, almost: D).
PS - Thanks for the pictures to our PM.