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.
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.
To ensure consistent code quality and adherence to our standards, we utilize the following tools:
Follow these steps to set up ESLint, TypeScript (based on Airbnb rules), and Prettier for your Angular projects:
Before you begin, make sure you have the following software installed:
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).
Install the following extensions in Visual Studio Code:
Note: Make sure these pluggins are installed in your code editor and enabled for your current workspace.
Navigate to the root directory of your project and execute the following npm commands:
-
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
-
Install dependencies to support typescript
npm install eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev
-
Install Prettier and related ESLint configurations
npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev
-
Integrate Angular ESLint schematics
ng add @angular-eslint/schematics
To ensure proper setup, add the following configuration files to your project's root directory:
-
.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" } ] }
-
tsconfig.eslint.json
Create atsconfig.eslint.json
file with the following content:{ "extends": "./tsconfig.json", "compilerOptions": { "noEmit": true }, "include": ["src/**/*.ts", "src/**/*.js", "test/**/*.ts", "src", "e2e", ".eslintrc.json"] }
-
.prettierrc.json
Create a.prettierrc.json
file with the following content:{ "trailingComma": "all", "tabWidth": 2, "semi": true, "singleQuote": true, "bracketSpacing": true, "printWidth": 100 }
-
package.json
Updatepackage.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)\"" } // ... }
-
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" } }
-
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.
-
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
-
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.
-
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.