Skip to content

Latest commit

 

History

History
617 lines (553 loc) · 20.8 KB

angular.md

File metadata and controls

617 lines (553 loc) · 20.8 KB

Angular Coding Standards

Coding Style Guide

At Osmosys, we adhere to a comprehensive set of coding standards to ensure consistency and maintainability in our Angular projects. Our coding style is based on the guidelines provided by the Angular Style Guide.

For TypeScript development, we follow the TypeScript guidelines established by Airbnb. You can find detailed TypeScript rules in the Airbnb TypeScript Style Guide.

For the complete list of TypeScript rules from Airbnb, please refer to this link.

Back to top

Security

Security is a top priority in our development process. We implement security practices outlined in the Angular Security Guide to safeguard our applications against common web application vulnerabilities and attacks, including cross-site scripting (XSS) attacks.

Back to top

Standard Enforcing Tools

To ensure consistent code quality and adherence to our standards, we utilize the following tools:

Back to top

Setting up ESLint, TypeScript (Airbnb rules), and Prettier

Follow these steps to set up ESLint, TypeScript (based on Airbnb rules), and Prettier for your Angular projects:

Prerequisites

Before you begin, make sure you have the following software installed:

  1. Node.js
  2. npm
  3. Angular CLI
  4. Visual Studio Code

It is highly advisable to ensure that you are working with the latest version of Angular. If your project is currently on a lower version, its recommended upgrading it to Angular 13 or a more recent stable release. (In case you decide not to upgrade, please make sure to resolve the dependency tree meticulously after installing the following packages. This will help maintain compatibility and ensure a smooth development process).

Back to top

Visual Studio Code Extensions

Install the following extensions in Visual Studio Code:

  1. ESLint
  2. Prettier

Note: Make sure these pluggins are installed in your code editor and enabled for your current workspace.

Back to top

NPM Commands

Navigate to the root directory of your project and execute the following npm commands:

  1. Initialize ESLint configuration

    npm init @eslint/config

    Choose the following options:

    • Usage: To check syntax and find problems
    • Modules: JavaScript (import/export)
    • Frameworks: None of these
    • Typescript: No (Airbnb does not have support for TS, it is defined for JS and we will add additional TS support for it later)
    • Environment: Browser

    Now select the option to install required dependencies. Click on Yes. Select the appropiate package manager that is used by you.

    Refer to this screenshot for the options to choose and expected outcome

  2. Install dependencies to support typescript

    npm install eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev
  3. Install Prettier and related ESLint configurations

    npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev
  4. Integrate Angular ESLint schematics

    ng add @angular-eslint/schematics

Back to top

Files to be created/modified in root directory

To ensure proper setup, add the following configuration files to your project's root directory:

  1. .eslintrc.json
    Replace all data in .eslintrc.json with the following

    {
      "env": {
        "es6": true,
        "browser": true,
        "node": true
      },
      "extends": ["plugin:@angular-eslint/recommended", "plugin:prettier/recommended"],
      "rules": { "prettier/prettier": "error" },
      // Eslint for HTML files
      "overrides": [
        {
          "files": ["*.html", "*.component.html"],
          // We set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting
          "parserOptions": {
            "project": ["tsconfig.(app|spec).json"]
          },
          "extends": [
            "plugin:@angular-eslint/template/recommended"
            // "plugin:@angular-eslint/template/accessibility" //TODO: Look for a possible solution to add this option.
          ],
          "rules": {
            // Custom rules for HTML by Osmosys
            "max-len": ["warn", { "code": 100 }]
          }
        },
        // Custom rules for TypeScript
        {
          "files": ["*.ts"],
          "extends": [
            // Added base
            "airbnb-base",
            "airbnb-typescript/base",
            // Added modern prettier
            "prettier"
          ],
          "parser": "@typescript-eslint/parser",
          "parserOptions": {
            "parser": "@typescript-eslint/parser",
            "ecmaVersion": "latest",
            "project": ["./tsconfig.eslint.json", "./tsconfig.json"]
          },
          "rules": {
            // Custom rules for typescript by Osmosys
            "@typescript-eslint/no-explicit-any": "error",
            "padding-line-between-statements": [
              "error",
              { "blankLine": "always", "prev": "*", "next": "function" },
              { "blankLine": "always", "prev": "function", "next": "*" },
              { "blankLine": "always", "prev": "*", "next": "if" },
              { "blankLine": "always", "prev": "if", "next": "*" },
              { "blankLine": "always", "prev": "*", "next": "for" },
              { "blankLine": "always", "prev": "for", "next": "*" },
              { "blankLine": "always", "prev": "*", "next": "while" },
              { "blankLine": "always", "prev": "while", "next": "*" }
            ],
            "arrow-body-style": [
              "error",
              "as-needed",
              {
                "requireReturnForObjectLiteral": false
              }
            ],
            "@typescript-eslint/prefer-function-type": "error",
            "@typescript-eslint/naming-convention": "error",
            "capitalized-comments": [
              "off",
              "never",
              {
                "line": {
                  "ignorePattern": ".*",
                  "ignoreInlineComments": true,
                  "ignoreConsecutiveComments": true
                },
                "block": {
                  "ignorePattern": ".*",
                  "ignoreInlineComments": true,
                  "ignoreConsecutiveComments": true
                }
              }
            ],
            "spaced-comment": [
              "error",
              "always",
              {
                "line": {
                  "exceptions": ["-", "+"],
                  "markers": ["=", "!", "/"]
                },
                "block": {
                  "exceptions": ["-", "+"],
                  "markers": ["=", "!", ":", "::"],
                  "balanced": true
                }
              }
            ],
            "eol-last": ["error", "always"],
            "guard-for-in": "error",
            "no-restricted-imports": [
              "off",
              {
                "paths": [],
                "patterns": []
              }
            ],
            "indent": [
              "error",
              2,
              {
                "SwitchCase": 1,
                "VariableDeclarator": 1,
                "outerIIFEBody": 1,
                "FunctionDeclaration": {
                  "parameters": 1,
                  "body": 1
                },
                "FunctionExpression": {
                  "parameters": 1,
                  "body": 1
                },
                "CallExpression": {
                  "arguments": 1
                },
                "ArrayExpression": 1,
                "ObjectExpression": 1,
                "ImportDeclaration": 1,
                "flatTernaryExpressions": false,
                "ignoredNodes": [
                  "JSXElement",
                  "JSXElement > *",
                  "JSXAttribute",
                  "JSXIdentifier",
                  "JSXNamespacedName",
                  "JSXMemberExpression",
                  "JSXSpreadAttribute",
                  "JSXExpressionContainer",
                  "JSXOpeningElement",
                  "JSXClosingElement",
                  "JSXFragment",
                  "JSXOpeningFragment",
                  "JSXClosingFragment",
                  "JSXText",
                  "JSXEmptyExpression",
                  "JSXSpreadChild"
                ],
                "ignoreComments": false
              }
            ],
            "@typescript-eslint/consistent-type-definitions": "error",
            "@typescript-eslint/explicit-member-accessibility": "off",
            "@typescript-eslint/member-ordering": [
              "error",
              {
                "default": ["static-field", "instance-field", "static-method", "instance-method"]
              }
            ],
            "no-empty-function": [
              "error",
              {
                "allow": ["arrowFunctions", "functions", "methods", "constructors"]
              }
            ],
            "no-bitwise": "error",
            "no-new-wrappers": "error",
            "no-debugger": "error",
            "constructor-super": "error",
            "no-empty": "error",
            "@typescript-eslint/no-empty-interface": "error",
            "no-eval": "error",
            "@typescript-eslint/no-inferrable-types": "error",
            "@typescript-eslint/no-misused-new": "error",
            "@typescript-eslint/no-non-null-assertion": "error",
            "no-shadow": "off",
            "@typescript-eslint/no-shadow": ["error"],
            "dot-notation": [
              "error",
              {
                "allowKeywords": true
              }
            ],
            "no-throw-literal": "error",
            "no-fallthrough": "error",
            "no-trailing-spaces": [
              "error",
              {
                "skipBlankLines": false,
                "ignoreComments": false
              }
            ],
            "no-undef-init": "error",
            "no-unused-expressions": [
              "error",
              {
                "allowShortCircuit": false,
                "allowTernary": false,
                "allowTaggedTemplates": false
              }
            ],
            "no-var": "error",
            "sort-keys": [
              "off",
              "asc",
              {
                "caseSensitive": false,
                "natural": true
              }
            ],
            "brace-style": [
              "error",
              "1tbs",
              {
                "allowSingleLine": true
              }
            ],
            "prefer-const": [
              "error",
              {
                "destructuring": "any",
                "ignoreReadBeforeAssign": true
              }
            ],
            "quotes": [
              "error",
              "single",
              {
                "avoidEscape": true
              }
            ],
            "class-methods-use-this": "off",
            "radix": "error",
            "eqeqeq": [
              "error",
              "always",
              {
                "null": "ignore"
              }
            ],
            "@typescript-eslint/type-annotation-spacing": "error",
            "@typescript-eslint/unified-signatures": "error",
            "no-multi-spaces": [
              "error",
              {
                "ignoreEOLComments": false
              }
            ],
            "@angular-eslint/no-output-on-prefix": "error",
            "@angular-eslint/no-inputs-metadata-property": "error",
            "@angular-eslint/no-outputs-metadata-property": "error",
            "@angular-eslint/no-host-metadata-property": "error",
            "@angular-eslint/use-lifecycle-interface": "error",
            "@angular-eslint/use-pipe-transform-interface": "error",
            "@angular-eslint/component-class-suffix": "error",
            "@angular-eslint/directive-class-suffix": "error",
            "import/no-extraneous-dependencies": [
              "error",
              {
                "devDependencies": false,
                "optionalDependencies": false,
                "peerDependencies": false
              }
            ],
    
            // Additional Custom Rules
    
            "camelcase": [
              "error",
              {
                "properties": "never",
                "ignoreDestructuring": false
              }
            ],
            "comma-dangle": [
              "error",
              {
                "arrays": "always-multiline",
                "objects": "always-multiline",
                "imports": "always-multiline",
                "exports": "always-multiline",
                "functions": "always-multiline"
              }
            ],
            "comma-spacing": [
              "error",
              {
                "before": false,
                "after": true
              }
            ],
            "default-param-last": "error",
    
            "func-call-spacing": ["error", "never"],
    
            "keyword-spacing": [
              "error",
              {
                "before": true,
                "after": true,
                "overrides": {
                  "return": {
                    "after": true
                  },
                  "throw": {
                    "after": true
                  },
                  "case": {
                    "after": true
                  }
                }
              }
            ],
            "lines-between-class-members": [
              "error",
              "always",
              {
                "exceptAfterSingleLine": false
              }
            ],
            "no-array-constructor": "error",
            "no-dupe-class-members": "error",
    
            "no-extra-parens": [
              "off",
              "all",
              {
                "conditionalAssign": true,
                "nestedBinaryExpressions": false,
                "returnAssign": false,
                "ignoreJSX": "all", // delegate to eslint-plugin-react
                "enforceForArrowConditionals": false
              }
            ],
            "no-extra-semi": "error",
            "no-implied-eval": "error",
            "no-new-func": "error",
            "no-loss-of-precision": "error",
            "no-loop-func": "error",
            "no-magic-numbers": [
              "off",
              {
                "ignore": [],
                "ignoreArrayIndexes": true,
                "enforceConst": true,
                "detectObjects": false
              }
            ],
            "no-redeclare": "error",
            "space-before-blocks": "error",
    
            "no-unused-vars": [
              "error",
              {
                "vars": "all",
                "args": "after-used",
                "ignoreRestSiblings": true
              }
            ],
            "no-use-before-define": [
              "error",
              {
                "functions": true,
                "classes": true,
                "variables": true
              }
            ],
            "no-useless-constructor": "off",
            "@typescript-eslint/no-useless-constructor": "error",
            "semi": ["error", "always"],
            "space-before-function-paren": [
              "error",
              {
                "anonymous": "always",
                "named": "never",
                "asyncArrow": "always"
              }
            ],
            "require-await": "off",
            "no-return-await": "error",
            "space-infix-ops": "error",
            "object-curly-spacing": ["error", "always"],
            "import/prefer-default-export": "off"
          }
        },
        // Configuration for unit and e2e spec files
        {
          "files": ["*.spec.ts"],
          "rules": {
            "@typescript-eslint/no-unused-vars": "off"
          }
        },
        /**
        * This extra piece of configuration is only necessary if you make use of inline
        * templates within Component metadata, e.g.:
        */
        {
          "files": ["*.component.ts"],
          "parser": "@typescript-eslint/parser",
          "parserOptions": {
            "ecmaVersion": 2020,
            "sourceType": "module"
          },
          "plugins": ["@angular-eslint/template", "prettier"],
          "processor": "@angular-eslint/template/extract-inline-html"
        }
      ]
    }
  2. tsconfig.eslint.json
    Create a tsconfig.eslint.json file with the following content:

    {
      "extends": "./tsconfig.json",
      "compilerOptions": {
        "noEmit": true
      },
      "include": ["src/**/*.ts", "src/**/*.js", "test/**/*.ts", "src", "e2e", ".eslintrc.json"]
    }
    
  3. .prettierrc.json
    Create a .prettierrc.json file with the following content:

    {
      "trailingComma": "all",
      "tabWidth": 2,
      "semi": true,
      "singleQuote": true,
      "bracketSpacing": true,
      "printWidth": 100
    }
  4. package.json
    Update package.json. Add the following lines under "scripts" to detects any warnings and enable Prettier formatting:

    // package.json
    {
      // ...
      "scripts": {
        // Other Commands
        // Add the following line under "scripts"
        "lint": "ng lint --max-warnings 0",
        "prettier-format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\""
      }
      // ...
    }
  5. settings.json
    In your project's root directory, locate the .vscode folder. Create one if it doesn't exist. Once inside the folder, you can either create or modify the settings.json file. Populate this file with the following content:

      {
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.formatOnSave": true,
        "[javascript]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[typescript]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[css]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[html]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        }
      }

Back to top

Testing and Commands

  1. Linting: Check for Code Issues
    Use the following command to analyze your code for potential issues:

    ng lint

    This command generates a comprehensive report highlighting any problems present in your current codebase.

  2. Auto-fix Linting Issues
    If the ng lint command detects fixable issues, you can automatically resolve some of them using:

    ng lint --fix

    By running this command, you allow the linter to automatically address certain issues reported by eslint

  3. Formatting: Prettier Rules Enforcement
    To ensure consistent code formatting according to Prettier rules, utilize the command:

    npm run prettier-format

    Executing this command will format your entire codebase in alignment with the specified Prettier configuration (this is based on the files mentioned in your package.json). This is only recommended for the first time your are enforcing prettier rules and want to format all your code according to specified rules.

  4. Formatting on Save
    Upon completing the mentioned steps, your files will be automatically formatted to adhere to the guidelines outlined in .prettierrc.json whenever you save them. This integration streamlines the process of maintaining a uniform code style.

Back to top