Linting in Angular

Linting in Angular

We have all been using Angular for more than a year now. One thing I believe we haven't been paying attention to is linting. Angular-cli provides support for linting by default. It uses codelyzer and ts-lint for this purpose. All you have to do is run the command 'ng lint' on command line and it will display all the linting errors. Or just use Visual Studio Code. In this IDE, linting errors are displayed directly in your ts files. Another tool to improve your code quality further is SonarQube.

Ts lint

There is a tslint.json file inside your Angular-cli project. It contains a set of rules for linting. You can use it as it is, but here are a few things you might want to modify:

  1. max-line-length: The default value is 140. I changed it to 180 so that it is in sync with the max line length of SonarQube.
  2. component-selector-prefix: If you are using a different component selector prefix other than app, you will have to change this. Otherwise linter will throw error for all your components.

Now I will elaborate on the common linting errors and how to fix them.

  1. " should be ': According to this rule, you should use single quotes instead of double quotes inside a ts file. This is something which I believe should be flexible. Talk to your team members and see what they prefer. I personally think that we should use single quotes. Have a look at the this: https://stackoverflow.com/questions/25453864/what-is-the-standard-for-use-of-quotes-in-typescript
  2. Unused imports: This is a pain to maintain in Angular components, especially if you are using Atom. You have to manually check and remove unused imports. The solution - use Visual Studio Code and install the plugin Typescript Hero. All you have to do now is open your ts file and use the shortcut cmd+alt+o. Not only will it clear unused imports but also order your imports alphabetically.
  3. Identifier 'variable' is never reassigned; use 'const' instead of 'let': In a function, when you declare a variable and if it is not being reassigned, use const instead of let.
  4. variable is declared but its value is never read: Remove unused variables. There is a pitfall here though. If you declare a private global variable and do not use it in the ts file, linter will throw this error. But you might be using it in your template HTML file. So before removing the variable, check whether it is being used in your template. I think all of you are aware that when you build for production using 'ng build prod', it throw an error if a private variable are being accessed in templates. But if the error is inside a lazy loaded module, somehow this error is bypassed. Solution - Add 'fullTemplateTypeCheck': true to angularCompilerOptions inside your tsconfig.json file.
  5. Forbidden 'var' keyword, use 'let' or 'const' instead: I believe this one is self explanatory.
  6. == should be ===: This one came as a surprise to me. I was using == everywhere. This is OK if you are using proper types and always comparing variables which are of the same type. But still, to be on the safer side, use === and !== instead of == and !=. If you want to read more on this one have a look at the following link: https://www.quora.com/in/Why-does-Typescript-have-instead-of-defaulting-to-since-it-was-created-to-encourage-type-safety.
  7. Type number trivially inferred from a number literal, remove type annotation: When you are initializing a variable while declaring it, there is no need to declare it's type. This goes for other variable types like boolean, string as well.
const year: number = 2018 // Instead of this
const year = 2018 // use this
  1. Exceeds maximum line length of 180: One line in your code should not exceed 180 characters. If it does, then refactor it so that it's broken down to multiple lines. This improves the code readability.
  2. Shadowed name: 'element': Don't redeclare the same variable multiple times within a function. Have a look at the example below:
this.iotDeviceService.findAll(false, 1, 10).subscribe(
            data => { // Here
                let totalElements = data["totalElements"];

                if (totalElements > 0) {
                    this.iotDeviceService.findAll(false, 1, totalElements).subscribe(
                        data => { // This variable is being declared again. Avoid this
                            this.iotDevices = data["content"];
                        });
                }
            });

SonarQube

I will start with how to configure SonarQube for typescript and then walk you through some of the common errors which it displays, apart from the ones displayed by ts lint.

Configuration

sonar.projectKey=teckportal-frontend
sonar.projectName=Teckportal Frontend
sonar.projectVersion=1.0
sonar.sourceEncoding=UTF-8
sonar.sources=src
sonar.exclusions=**/node_modules/**,**/*.spec.ts
sonar.tests=e2e
sonar.test.inclusions=**/*.e2e-spec.ts,**/*.po.ts
sonar.ts.tslintconfigpath=tslint.json
  • Extract sources from the downloaded SonarQube zip file. Goto bin folder, select your OS and run the following command:
    ./sonar.sh console
    This will start SonarQube.
  • Extract sources from the downloaded Sonar scanner zip file. Goto your project folder and run the following command:
    /sonar-scanner -Dsonar.sources=.
  • Now access SonarQube using [http://localhost:9000/projects]. You will see your project there.
  • Click on your project. In Administration tab, select Quality Profiles.
  • Change quality profile of TypeScript to Sonar way recommended
  • Now run the sonar scanner again.

Common TypeScript errors displayed in SonarQube

  1. Define a constant instead of duplicating this literal 3 times.: If you have a string literal being used multiple times, declare it as a constant.
new HttpHeaders().append('Content-Type', 'application/json')

If 'Content-Type' is used multiple times, declare it as a constant.
2. Private member variable is never reassigned; mark it as 'readonly': If a global private variable is declared and it is not re assigned, declare it as readonly. This improves the readability of your code.

private ngUnsubscribe: Subject<void> = new Subject<void>(); //Instead of this
private readonly ngUnsubscribe: Subject<void> = new Subject<void>();// Use this
  1. Refactor this method to reduce its Cognitive Complexity: If your method has too many loops and if conditions, the complexity of the function increases. Refactor it.
  2. Use a template literal instead of concatenating with a string literal: Have a look at the code below:
this.usersLink = '/company/' + this.companyId + '/users'; // Replace this
this.usersLink = `/company/${this.companyId}/users`; // With this

This is another thing which improves the readability of your code.
5. This function has 10 parameters, which is greater than the 7 authorized: This happens in 3 cases -

  • A normal function has more than 7 parameters. In this case, refactor the function.
  • Constructor of a class has more than 7 parameters. I believe most of us are using the following format for declaring a class:
export class AddressDTO {

    constructor(
        public street: string,
        public city: string,
        public country: string,
        public zip: number) { }
}

Here the only way to set variable values of the class is declaring a constructor. Avoid this. Better option is:

export class AddressDTO {
    public street: string;
    public city: string;
    public country: string;
    public zip: number;
}
  • Component constructor has more than 7 parameters. As we know, Angular component constructors are used for dependency injection. If we have many dependences inside a component, SonarQube will display an error. This error, can be ignored. But if anyone finds a good solution on how to handle this, please let me know. I will update this post.

Conclusion

Usage of ts lint and SonarQube in tandem will improve your code quality tremendously. Make it a habit. Initially, refactoring everything will be a bit of an overhead, but I believe it's worth the hassle. Feel free to contact me if you have any questions.