Automate code quality with linters, formatters, and type checkers
Automate code quality with linters, formatters, and type checkers
Code quality tools help catch bugs early, maintain consistency, and improve code readability. Understanding the three main categories is essential.
1. Linters (Code Analysis):
Linters analyze your code for potential errors, bugs, and style violations without executing it.
1// ❌ Linter catches these issues:
2function getUserName(user) {
3 return user.name; // ⚠️ user might be null/undefined
4}
5
6var x = 10; // ⚠️ var is discouraged, use const/let
7if (x = 5) { // ⚠️ Assignment in condition (should be ===)
8 console.log(x);
9}
10
11const unused = 'value'; // ⚠️ Unused variable
12
13// ✅ Linter-compliant code:
14function getUserName(user) {
15 if (!user) {
16 throw new Error('User is required');
17 }
18 return user.name;
19}
20
21const x = 10;
22if (x === 5) {
23 console.log(x);
24}2. Formatters (Code Style):
Formatters automatically format your code to match a consistent style.
1// ❌ Inconsistent formatting:
2function calculateTotal( items, tax ){
3const subtotal=items.reduce((sum,item)=>sum+item.price,0)
4 return subtotal*(1+tax)}
5
6// ✅ After Prettier formatting:
7function calculateTotal(items, tax) {
8 const subtotal = items.reduce((sum, item) => sum + item.price, 0);
9 return subtotal * (1 + tax);
10}3. Type Checkers (Type Safety):
Type checkers verify that your code uses the correct data types.
1// ❌ Type errors caught at compile time:
2function addNumbers(a: number, b: number): number {
3 return a + b;
4}
5
6addNumbers("5", "10"); // ❌ Error: Expected number, got string
7addNumbers(5, 10).toUpperCase(); // ❌ Error: number has no toUpperCase
8
9// ✅ Type-safe code:
10function addNumbers(a: number, b: number): number {
11 return a + b;
12}
13
14const result: number = addNumbers(5, 10); // ✅ CorrectWhy Use All Three:
1// Without tools:
2function processUser(user) {
3 const name=user.name.toUpperCase()
4 if(name==='ADMIN'){
5 return user
6 }
7}
8
9// Linter catches:
10// - Missing semicolons
11// - Potential null reference (user.name)
12// - Inconsistent equality (should use ===)
13
14// Formatter fixes:
15// - Spacing around operators
16// - Consistent indentation
17// - Semicolon usage
18
19// Type checker prevents:
20// - Passing wrong types
21// - Accessing non-existent properties
22// - Return type mismatches
23
24// ✅ With all three:
25function processUser(user: User | null): User | null {
26 if (!user || !user.name) {
27 return null;
28 }
29
30 const name = user.name.toUpperCase();
31 if (name === 'ADMIN') {
32 return user;
33 }
34
35 return null;
36}Benefits of Code Quality Tools:
The Quality Workflow:
1# 1. Write code
2vim src/user.js
3
4# 2. Formatter runs on save (Prettier)
5# Code is automatically formatted
6
7# 3. Linter runs in background (ESLint)
8# Issues highlighted in editor
9
10# 4. Type checker runs (TypeScript)
11# Type errors shown in problems panel
12
13# 5. Pre-commit hook runs
14git commit -m "Add user feature"
15# All three tools run automatically
16# Commit blocked if issues found
17
18# 6. CI/CD runs all checks
19git push
20# Build fails if quality checks failTool Comparison:
| Tool | Purpose | When It Runs | Can Fix |
|------|---------|--------------|---------|
| ESLint | Find code issues | On save, pre-commit | Some rules |
| Prettier | Format code style | On save, pre-commit | All styling |
| TypeScript | Check types | Continuous, build | No |
| Husky | Git hooks | Pre-commit/push | N/A |
| lint-staged | Run on changed files | Pre-commit | N/A |
ESLint is the industry-standard linter for JavaScript and TypeScript. It's highly configurable and extensible.
Installing ESLint:
1# Initialize ESLint in your project
2npm init @eslint/config
3
4# Or manual installation
5npm install --save-dev eslint @eslint/js
6
7# For TypeScript
8npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
9
10# For React
11npm install --save-dev eslint-plugin-react eslint-plugin-react-hooks
12
13# For Next.js (includes React rules)
14npm install --save-dev eslint-config-nextESLint Configuration (.eslintrc.json):
1{
2 "env": {
3 "browser": true,
4 "es2021": true,
5 "node": true
6 },
7 "extends": [
8 "eslint:recommended",
9 "plugin:@typescript-eslint/recommended",
10 "plugin:react/recommended",
11 "plugin:react-hooks/recommended"
12 ],
13 "parser": "@typescript-eslint/parser",
14 "parserOptions": {
15 "ecmaVersion": "latest",
16 "sourceType": "module",
17 "ecmaFeatures": {
18 "jsx": true
19 }
20 },
21 "plugins": [
22 "@typescript-eslint",
23 "react",
24 "react-hooks"
25 ],
26 "rules": {
27 // Error prevention
28 "no-unused-vars": "error",
29 "no-undef": "error",
30 "no-console": "warn",
31
32 // Best practices
33 "eqeqeq": ["error", "always"],
34 "no-var": "error",
35 "prefer-const": "error",
36 "prefer-arrow-callback": "warn",
37
38 // TypeScript specific
39 "@typescript-eslint/no-explicit-any": "warn",
40 "@typescript-eslint/explicit-function-return-type": "off",
41 "@typescript-eslint/no-unused-vars": ["error", {
42 "argsIgnorePattern": "^_"
43 }],
44
45 // React specific
46 "react/react-in-jsx-scope": "off", // Not needed in React 17+
47 "react/prop-types": "off", // Using TypeScript instead
48 "react-hooks/rules-of-hooks": "error",
49 "react-hooks/exhaustive-deps": "warn"
50 },
51 "settings": {
52 "react": {
53 "version": "detect"
54 }
55 }
56}ESLint Rules Categories:
1// 1. POSSIBLE ERRORS - Catch bugs
2function calculateTotal(items) {
3 let total = 0;
4 for (const item of items) {
5 total += item.price; // ✅ ESLint: Correct
6 }
7 return totall; // ❌ ESLint: 'totall' is not defined
8}
9
10// 2. BEST PRACTICES - Better code
11if (user.role == 'admin') { // ❌ ESLint: Use === instead of ==
12 grantAccess();
13}
14
15if (user.role === 'admin') { // ✅ ESLint: Correct
16 grantAccess();
17}
18
19// 3. VARIABLES - Proper variable usage
20var oldStyle = 'bad'; // ❌ ESLint: Use let or const instead of var
21const modernStyle = 'good'; // ✅ ESLint: Correct
22
23const unused = 'value'; // ❌ ESLint: 'unused' is assigned but never used
24
25// 4. STYLISTIC - Code style (better handled by Prettier)
26function foo(){return 'bar'} // ❌ ESLint: Missing spaces
27
28function foo() { // ✅ ESLint: Correct formatting
29 return 'bar';
30}
31
32// 5. ES6+ - Modern JavaScript
33items.forEach(function(item) { // ⚠️ ESLint: Use arrow function
34 console.log(item);
35});
36
37items.forEach(item => { // ✅ ESLint: Better
38 console.log(item);
39});Common ESLint Rules Explained:
1// no-unused-vars - Variables must be used
2const apiKey = 'secret'; // ❌ Declared but never used
3const config = { timeout: 5000 };
4console.log(config); // ✅ Used
5
6// no-undef - Variables must be defined
7console.log(undefinedVar); // ❌ undefinedVar is not defined
8const definedVar = 'value';
9console.log(definedVar); // ✅ Defined
10
11// eqeqeq - Require === instead of ==
12if (value == '5') { // ❌ Use ===
13 // This is true for value = 5 or value = '5'
14}
15
16if (value === '5') { // ✅ Strict equality
17 // Only true for string '5'
18}
19
20// no-var - Use let/const instead of var
21var x = 10; // ❌ var has function scope
22let y = 10; // ✅ let has block scope
23const z = 10; // ✅ const for immutable bindings
24
25// prefer-const - Use const when variable is not reassigned
26let name = 'John'; // ❌ Should be const
27name = 'Jane'; // If we never reassign
28
29const name = 'John'; // ✅ Use const
30
31// no-console - Warn about console.log in production
32console.log('Debug info'); // ⚠️ Warning: Remove before production
33logger.info('Debug info'); // ✅ Use proper logger
34
35// @typescript-eslint/no-explicit-any - Avoid 'any' type
36function process(data: any) { // ⚠️ Avoid any, use specific type
37 return data;
38}
39
40function process(data: UserData) { // ✅ Specific type
41 return data;
42}Running ESLint:
1# Lint all files
2npx eslint .
3
4# Lint specific files
5npx eslint src/**/*.ts
6
7# Auto-fix fixable issues
8npx eslint . --fix
9
10# Lint and see detailed output
11npx eslint . --format=table
12
13# Package.json scripts:
14{
15 "scripts": {
16 "lint": "eslint .",
17 "lint:fix": "eslint . --fix",
18 "lint:check": "eslint . --max-warnings=0"
19 }
20}Ignoring Files (.eslintignore):
1# .eslintignore
2node_modules/
3dist/
4build/
5coverage/
6*.min.js
7.next/
8out/
9public/
10
11# Generated files
12*.generated.ts
13**/*.d.tsVS Code Integration:
1// .vscode/settings.json
2{
3 "eslint.validate": [
4 "javascript",
5 "javascriptreact",
6 "typescript",
7 "typescriptreact"
8 ],
9 "editor.codeActionsOnSave": {
10 "source.fixAll.eslint": true
11 },
12 "eslint.run": "onType",
13 "eslint.format.enable": true
14}Custom Rules:
1// .eslintrc.json
2{
3 "rules": {
4 // Require specific naming conventions
5 "camelcase": ["error", {
6 "properties": "never",
7 "ignoreDestructuring": true
8 }],
9
10 // Limit function complexity
11 "complexity": ["warn", 10],
12
13 // Limit function parameters
14 "max-params": ["warn", 4],
15
16 // Require return types (TypeScript)
17 "@typescript-eslint/explicit-function-return-type": ["warn", {
18 "allowExpressions": true
19 }],
20
21 // Custom project-specific rules
22 "no-restricted-imports": ["error", {
23 "patterns": ["../*"] // Prevent relative imports
24 }]
25 }
26}Prettier is an opinionated code formatter that enforces consistent style across your entire codebase.
Installing Prettier:
1# Install Prettier
2npm install --save-dev prettier
3
4# Install ESLint-Prettier integration
5npm install --save-dev eslint-config-prettier eslint-plugin-prettierPrettier Configuration (.prettierrc.json):
1{
2 "semi": true,
3 "trailingComma": "es5",
4 "singleQuote": true,
5 "printWidth": 100,
6 "tabWidth": 2,
7 "useTabs": false,
8 "arrowParens": "always",
9 "endOfLine": "lf",
10 "bracketSpacing": true,
11 "jsxSingleQuote": false,
12 "jsxBracketSameLine": false,
13 "proseWrap": "preserve"
14}Before and After Prettier:
1// ❌ Before Prettier - Inconsistent formatting
2function calculateTotal(items,tax)
3{
4const subtotal=items.reduce((sum,item)=>
5{return sum+item.price},0);
6 const total=subtotal*(1+tax)
7return total;}
8
9const user={name:"John",age:30,email:"[email protected]"}
10
11// ✅ After Prettier - Consistent, readable
12function calculateTotal(items, tax) {
13 const subtotal = items.reduce((sum, item) => {
14 return sum + item.price;
15 }, 0);
16 const total = subtotal * (1 + tax);
17 return total;
18}
19
20const user = {
21 name: 'John',
22 age: 30,
23 email: '[email protected]',
24};Prettier Configuration Options Explained:
1// semi: true - Add semicolons
2const x = 5; // ✅ With semicolon
3const x = 5 // ❌ Without
4
5// singleQuote: true - Use single quotes
6const name = 'John'; // ✅ Single quotes
7const name = "John"; // ❌ Double quotes
8
9// trailingComma: "es5" - Trailing commas where valid in ES5
10const obj = {
11 name: 'John',
12 age: 30, // ✅ Trailing comma (valid in ES5)
13};
14
15const arr = [
16 1,
17 2,
18 3, // ✅ Trailing comma
19];
20
21// printWidth: 100 - Wrap lines at 100 characters
22const longString = 'This is a very long string that exceeds the print width and will be wrapped';
23// Becomes:
24const longString =
25 'This is a very long string that exceeds the print width and will be wrapped';
26
27// tabWidth: 2, useTabs: false - 2 spaces for indentation
28function example() {
29 if (true) {
30 console.log('2 spaces'); // ✅ 2 space indentation
31 }
32}
33
34// arrowParens: "always" - Always include parens around arrow function params
35const double = (x) => x * 2; // ✅ Parens around single param
36const double = x => x * 2; // ❌ No parens
37
38// bracketSpacing: true - Spaces inside object braces
39const obj = { name: 'John' }; // ✅ Spaces inside braces
40const obj = {name: 'John'}; // ❌ No spacesLanguage-Specific Formatting:
1// JavaScript/TypeScript
2const config = {
3 name: 'app',
4 version: '1.0.0',
5 settings: {
6 debug: true,
7 timeout: 5000,
8 },
9};
10
11// JSON - Always double quotes
12{
13 "name": "app",
14 "version": "1.0.0"
15}
16
17// HTML/JSX
18<UserProfile
19 name="John Doe"
20 age={30}
21 email="[email protected]"
22 onUpdate={handleUpdate}
23/>
24
25// CSS
26.container {
27 display: flex;
28 flex-direction: column;
29 gap: 1rem;
30 padding: 2rem;
31}
32
33// Markdown
34# Heading
35
36- List item 1
37- List item 2
38
39\`\`\`javascript
40code block
41\`\`\`Prettier Ignore (.prettierignore):
1# .prettierignore
2node_modules/
3dist/
4build/
5coverage/
6*.min.js
7*.min.css
8.next/
9out/
10
11# Keep original formatting for these
12CHANGELOG.md
13LICENSE
14package-lock.json
15pnpm-lock.yamlRunning Prettier:
1# Format all files
2npx prettier --write .
3
4# Check formatting without modifying files
5npx prettier --check .
6
7# Format specific files
8npx prettier --write "src/**/*.{js,jsx,ts,tsx,json,css,md}"
9
10# Package.json scripts:
11{
12 "scripts": {
13 "format": "prettier --write .",
14 "format:check": "prettier --check ."
15 }
16}VS Code Integration:
1// .vscode/settings.json
2{
3 "editor.defaultFormatter": "esbenp.prettier-vscode",
4 "editor.formatOnSave": true,
5 "editor.formatOnPaste": true,
6
7 // Per-language formatter
8 "[javascript]": {
9 "editor.defaultFormatter": "esbenp.prettier-vscode"
10 },
11 "[typescript]": {
12 "editor.defaultFormatter": "esbenp.prettier-vscode"
13 },
14 "[json]": {
15 "editor.defaultFormatter": "esbenp.prettier-vscode"
16 },
17 "[markdown]": {
18 "editor.formatOnSave": false // Don't auto-format markdown
19 }
20}ESLint + Prettier Integration:
1// .eslintrc.json - Disable conflicting rules
2{
3 "extends": [
4 "eslint:recommended",
5 "plugin:@typescript-eslint/recommended",
6 "prettier" // Must be last to override other configs
7 ],
8 "plugins": ["prettier"],
9 "rules": {
10 "prettier/prettier": "error" // Show Prettier issues as ESLint errors
11 }
12}Team Configuration:
1// .editorconfig - Works across all editors
2root = true
3
4[*]
5charset = utf-8
6end_of_line = lf
7insert_final_newline = true
8trim_trailing_whitespace = true
9
10[*.{js,jsx,ts,tsx,json}]
11indent_style = space
12indent_size = 2
13
14[*.md]
15trim_trailing_whitespace = false
16
17// This ensures consistent formatting even without Prettier extensionCommon Prettier Workflows:
1# 1. Format on save (automatic)
2# Configured in VS Code settings
3
4# 2. Format before commit (automated)
5# Using husky + lint-staged (covered in next section)
6
7# 3. CI/CD check
8# In GitHub Actions:
9- name: Check formatting
10 run: npm run format:check
11
12# 4. Format entire project
13npm run format
14
15# 5. Format specific directory
16npx prettier --write "src/components/**/*.tsx"TypeScript adds static typing to JavaScript, catching errors at compile time instead of runtime.
Setting Up TypeScript:
1# Install TypeScript
2npm install --save-dev typescript @types/node @types/react @types/react-dom
3
4# Initialize TypeScript configuration
5npx tsc --init
6
7# For Next.js (automatically creates tsconfig.json)
8# Just create any .ts or .tsx fileTypeScript Configuration (tsconfig.json):
1{
2 "compilerOptions": {
3 // Language and Environment
4 "target": "ES2022",
5 "lib": ["ES2022", "DOM", "DOM.Iterable"],
6 "jsx": "preserve",
7
8 // Modules
9 "module": "ESNext",
10 "moduleResolution": "bundler",
11 "resolveJsonModule": true,
12 "allowJs": true,
13
14 // Emit
15 "declaration": true,
16 "declarationMap": true,
17 "sourceMap": true,
18 "outDir": "./dist",
19 "removeComments": true,
20 "noEmit": true, // Let bundler handle emit
21
22 // Type Checking (Strict Mode)
23 "strict": true,
24 "noImplicitAny": true,
25 "strictNullChecks": true,
26 "strictFunctionTypes": true,
27 "strictBindCallApply": true,
28 "strictPropertyInitialization": true,
29 "noImplicitThis": true,
30 "alwaysStrict": true,
31
32 // Additional Checks
33 "noUnusedLocals": true,
34 "noUnusedParameters": true,
35 "noImplicitReturns": true,
36 "noFallthroughCasesInSwitch": true,
37 "noUncheckedIndexedAccess": true,
38
39 // Interop
40 "esModuleInterop": true,
41 "allowSyntheticDefaultImports": true,
42 "forceConsistentCasingInFileNames": true,
43
44 // Skip lib check for faster compilation
45 "skipLibCheck": true,
46
47 // Path mapping
48 "baseUrl": ".",
49 "paths": {
50 "@/*": ["./src/*"],
51 "@/components/*": ["./src/components/*"],
52 "@/lib/*": ["./src/lib/*"]
53 }
54 },
55 "include": [
56 "src/**/*",
57 "next-env.d.ts"
58 ],
59 "exclude": [
60 "node_modules",
61 "dist",
62 "build"
63 ]
64}Type Safety Examples:
1// 1. BASIC TYPES - Catch type mismatches
2function greet(name: string): string {
3 return `Hello, ${name}!`;
4}
5
6greet("Alice"); // ✅ Correct
7greet(123); // ❌ Error: Argument of type 'number' not assignable to 'string'
8greet(); // ❌ Error: Expected 1 argument, got 0
9
10// 2. INTERFACES - Define object shapes
11interface User {
12 id: number;
13 name: string;
14 email: string;
15 role: 'admin' | 'user'; // Literal type
16 createdAt: Date;
17}
18
19function createUser(data: User): User {
20 return data;
21}
22
23createUser({
24 id: 1,
25 name: "Alice",
26 email: "[email protected]",
27 role: "admin",
28 createdAt: new Date()
29}); // ✅ Correct
30
31createUser({
32 id: 1,
33 name: "Bob"
34}); // ❌ Error: Missing required properties
35
36// 3. OPTIONAL PROPERTIES
37interface UserProfile {
38 name: string;
39 bio?: string; // Optional
40 avatar?: string;
41}
42
43function updateProfile(profile: UserProfile) {
44 console.log(profile.name); // ✅ Always available
45 console.log(profile.bio?.toUpperCase()); // ✅ Safe with optional chaining
46}
47
48// 4. NULL SAFETY with strictNullChecks
49function getUserName(user: User | null): string {
50 return user.name; // ❌ Error: user might be null
51}
52
53function getUserName(user: User | null): string {
54 if (!user) {
55 return "Unknown";
56 }
57 return user.name; // ✅ Correct - null check performed
58}
59
60// 5. ARRAY TYPES
61const numbers: number[] = [1, 2, 3];
62numbers.push(4); // ✅ Correct
63numbers.push("5"); // ❌ Error: Argument of type 'string' not assignable
64
65const users: User[] = [];
66users.push({ id: 1, name: "Alice", ... }); // ✅ Correct
67
68// 6. FUNCTION TYPES
69type ProcessFunction = (data: string) => Promise<void>;
70
71const processData: ProcessFunction = async (data) => {
72 // data is automatically typed as string
73 // Must return Promise<void>
74 console.log(data.toUpperCase());
75};
76
77// 7. GENERICS - Reusable type-safe components
78function firstElement<T>(arr: T[]): T | undefined {
79 return arr[0];
80}
81
82const num = firstElement([1, 2, 3]); // num: number | undefined
83const str = firstElement(["a", "b"]); // str: string | undefined
84
85// 8. TYPE GUARDS - Runtime type checking
86function isUser(obj: unknown): obj is User {
87 return (
88 typeof obj === 'object' &&
89 obj !== null &&
90 'id' in obj &&
91 'name' in obj
92 );
93}
94
95function processData(data: unknown) {
96 if (isUser(data)) {
97 // TypeScript knows data is User here
98 console.log(data.name); // ✅ Type-safe access
99 }
100}Common TypeScript Errors and Solutions:
1// ERROR: Object is possibly 'null' or 'undefined'
2const user = getUser(id);
3console.log(user.name); // ❌ Error
4
5// SOLUTION 1: Null check
6if (user) {
7 console.log(user.name); // ✅ Correct
8}
9
10// SOLUTION 2: Optional chaining
11console.log(user?.name); // ✅ Correct
12
13// SOLUTION 3: Non-null assertion (use carefully)
14console.log(user!.name); // ✅ Tells TS user is not null
15
16// ERROR: Property 'X' does not exist on type 'Y'
17interface User {
18 name: string;
19}
20
21const user: User = { name: "Alice" };
22console.log(user.age); // ❌ Error: Property 'age' does not exist
23
24// SOLUTION: Add property to interface
25interface User {
26 name: string;
27 age?: number; // Optional
28}
29
30// ERROR: Argument of type 'X' is not assignable to parameter of type 'Y'
31function greet(name: string) {
32 console.log(`Hello, ${name}`);
33}
34
35const num = 42;
36greet(num); // ❌ Error
37
38// SOLUTION: Convert type or fix function signature
39greet(String(num)); // ✅ Convert to string
40// Or
41function greet(name: string | number) { // Accept both
42 console.log(`Hello, ${name}`);
43}
44
45// ERROR: Type 'X' has no properties in common with type 'Y'
46interface Config {
47 timeout: number;
48 retries: number;
49}
50
51const config: Config = {
52 timeOut: 5000, // ❌ Typo: should be 'timeout'
53 retries: 3
54};
55
56// SOLUTION: Fix property name
57const config: Config = {
58 timeout: 5000, // ✅ Correct spelling
59 retries: 3
60};Advanced TypeScript Features:
1// 1. UTILITY TYPES
2interface User {
3 id: number;
4 name: string;
5 email: string;
6 password: string;
7}
8
9// Partial - Make all properties optional
10type PartialUser = Partial<User>;
11const updates: PartialUser = { name: "Alice" }; // ✅ Only name
12
13// Pick - Select specific properties
14type UserCredentials = Pick<User, 'email' | 'password'>;
15const creds: UserCredentials = {
16 email: "[email protected]",
17 password: "secret"
18};
19
20// Omit - Exclude specific properties
21type PublicUser = Omit<User, 'password'>;
22const publicUser: PublicUser = {
23 id: 1,
24 name: "Alice",
25 email: "[email protected]"
26 // password not allowed
27};
28
29// 2. MAPPED TYPES
30type Readonly<T> = {
31 readonly [P in keyof T]: T[P];
32};
33
34type ReadonlyUser = Readonly<User>;
35const user: ReadonlyUser = { id: 1, name: "Alice", ... };
36user.name = "Bob"; // ❌ Error: Cannot assign to readonly property
37
38// 3. CONDITIONAL TYPES
39type IsString<T> = T extends string ? true : false;
40
41type A = IsString<string>; // true
42type B = IsString<number>; // false
43
44// 4. DISCRIMINATED UNIONS
45type Success = {
46 status: 'success';
47 data: User;
48};
49
50type Error = {
51 status: 'error';
52 message: string;
53};
54
55type Result = Success | Error;
56
57function handleResult(result: Result) {
58 if (result.status === 'success') {
59 // TypeScript knows result is Success here
60 console.log(result.data.name); // ✅ Type-safe
61 } else {
62 // TypeScript knows result is Error here
63 console.log(result.message); // ✅ Type-safe
64 }
65}Running TypeScript Checks:
1# Type check all files
2npx tsc --noEmit
3
4# Watch mode for continuous type checking
5npx tsc --noEmit --watch
6
7# Package.json scripts:
8{
9 "scripts": {
10 "type-check": "tsc --noEmit",
11 "type-check:watch": "tsc --noEmit --watch"
12 }
13}Automate code quality checks before code reaches your repository using Git hooks.
What are Git Hooks?
Git hooks are scripts that run automatically when certain Git events occur (commit, push, etc.).
1# Git hooks lifecycle
2git add . # Stage changes
3git commit -m "msg" # Triggers pre-commit hook
4# → Husky runs
5# → lint-staged runs linters on staged files
6# → If all checks pass, commit proceeds
7# → If any check fails, commit is blockedInstalling Husky and lint-staged:
1# Install husky and lint-staged
2npm install --save-dev husky lint-staged
3
4# Initialize husky
5npx husky install
6
7# Add husky install to prepare script
8npm pkg set scripts.prepare="husky install"
9
10# Create pre-commit hook
11npx husky add .husky/pre-commit "npx lint-staged"Configuring lint-staged (package.json):
1{
2 "lint-staged": {
3 // JavaScript/TypeScript files
4 "*.{js,jsx,ts,tsx}": [
5 "eslint --fix",
6 "prettier --write",
7 "git add"
8 ],
9
10 // JSON files
11 "*.json": [
12 "prettier --write",
13 "git add"
14 ],
15
16 // CSS/SCSS files
17 "*.{css,scss}": [
18 "prettier --write",
19 "stylelint --fix",
20 "git add"
21 ],
22
23 // Markdown files
24 "*.md": [
25 "prettier --write",
26 "markdownlint --fix",
27 "git add"
28 ],
29
30 // Run type check on TypeScript files
31 "*.{ts,tsx}": [
32 "bash -c 'npm run type-check'"
33 ]
34 }
35}Alternative: .lintstagedrc.json:
1{
2 "*.{js,jsx,ts,tsx}": [
3 "eslint --fix",
4 "prettier --write"
5 ],
6 "*.{json,css,md}": [
7 "prettier --write"
8 ],
9 "*.{ts,tsx}": [
10 "bash -c 'tsc --noEmit'"
11 ]
12}Pre-commit Hook (.husky/pre-commit):
1#!/bin/sh
2. "$(dirname "$0")/_/husky.sh"
3
4echo "🔍 Running pre-commit checks..."
5
6# Run lint-staged
7npx lint-staged
8
9# Run tests on staged files (optional)
10npm run test:staged
11
12# Check for merge conflicts
13git diff --check
14
15echo "✅ Pre-commit checks passed!"Pre-push Hook (.husky/pre-push):
1#!/bin/sh
2. "$(dirname "$0")/_/husky.sh"
3
4echo "🚀 Running pre-push checks..."
5
6# Run all tests
7npm run test
8
9# Run full type check
10npm run type-check
11
12# Run full lint
13npm run lint
14
15# Build check
16npm run build
17
18echo "✅ Pre-push checks passed!"Commit Message Hook (.husky/commit-msg):
1#!/bin/sh
2. "$(dirname "$0")/_/husky.sh"
3
4# Enforce conventional commits
5npx --no -- commitlint --edit "$1"Commitlint Configuration (.commitlintrc.json):
1{
2 "extends": ["@commitlint/config-conventional"],
3 "rules": {
4 "type-enum": [
5 2,
6 "always",
7 [
8 "feat", // New feature
9 "fix", // Bug fix
10 "docs", // Documentation
11 "style", // Formatting
12 "refactor", // Code refactoring
13 "test", // Tests
14 "chore" // Maintenance
15 ]
16 ],
17 "subject-case": [2, "always", "sentence-case"]
18 }
19}
20
21// Valid commits:
22// ✅ feat: Add user authentication
23// ✅ fix: Resolve memory leak in cache
24// ✅ docs: Update API documentation
25
26// Invalid commits:
27// ❌ added new feature (missing type)
28// ❌ Fix: something (wrong case)Complete Workflow Example:
1# 1. Make changes to code
2vim src/user.ts
3
4# 2. Stage changes
5git add src/user.ts
6
7# 3. Attempt commit
8git commit -m "feat: Add user profile feature"
9
10# Husky triggers:
11# → lint-staged runs on src/user.ts
12# → ESLint checks code quality
13# → Prettier formats code
14# → TypeScript checks types
15# → All checks must pass
16
17# If any check fails:
18# ❌ Commit blocked
19# Error: ESLint found 2 errors
20
21# Fix the errors
22vim src/user.ts
23
24# Try again
25git add src/user.ts
26git commit -m "feat: Add user profile feature"
27# ✅ All checks passed! Commit successful
28
29# 4. Push to remote
30git push origin feature/user-profile
31
32# Pre-push hook runs:
33# → Full test suite
34# → Type check all files
35# → Build check
36# ✅ Push successfulBypassing Hooks (Use Sparingly):
1# Skip pre-commit hook (not recommended)
2git commit --no-verify -m "Quick fix"
3
4# Skip pre-push hook
5git push --no-verify
6
7# ⚠️ Only use in emergencies!
8# CI/CD will still run checksPackage.json Scripts Summary:
1{
2 "scripts": {
3 // Individual tools
4 "lint": "eslint .",
5 "lint:fix": "eslint . --fix",
6 "format": "prettier --write .",
7 "format:check": "prettier --check .",
8 "type-check": "tsc --noEmit",
9
10 // Combined checks
11 "check": "npm run lint && npm run type-check && npm run format:check",
12 "fix": "npm run lint:fix && npm run format",
13
14 // Git hooks
15 "prepare": "husky install",
16
17 // Testing
18 "test": "jest",
19 "test:staged": "jest --findRelatedTests",
20 "test:watch": "jest --watch",
21
22 // Build
23 "build": "next build",
24 "build:check": "tsc && next build"
25 },
26 "lint-staged": {
27 "*.{js,jsx,ts,tsx}": [
28 "eslint --fix",
29 "prettier --write"
30 ]
31 }
32}Integrate code quality checks into your CI/CD pipeline and establish team workflows.
GitHub Actions Workflow (.github/workflows/quality.yml):
1name: Code Quality
2
3on:
4 push:
5 branches: [main, develop]
6 pull_request:
7 branches: [main, develop]
8
9jobs:
10 quality:
11 runs-on: ubuntu-latest
12
13 steps:
14 - name: Checkout code
15 uses: actions/checkout@v3
16
17 - name: Setup Node.js
18 uses: actions/setup-node@v3
19 with:
20 node-version: '18'
21 cache: 'npm'
22
23 - name: Install dependencies
24 run: npm ci
25
26 - name: Run ESLint
27 run: npm run lint
28
29 - name: Run Prettier check
30 run: npm run format:check
31
32 - name: Run TypeScript check
33 run: npm run type-check
34
35 - name: Run tests
36 run: npm test -- --coverage
37
38 - name: Upload coverage
39 uses: codecov/codecov-action@v3
40 with:
41 files: ./coverage/coverage-final.json
42
43 - name: Build check
44 run: npm run build
45
46 - name: Comment PR with results
47 if: github.event_name == 'pull_request'
48 uses: actions/github-script@v6
49 with:
50 script: |
51 github.rest.issues.createComment({
52 issue_number: context.issue.number,
53 owner: context.repo.owner,
54 repo: context.repo.repo,
55 body: '✅ All quality checks passed!'
56 })GitLab CI/CD (.gitlab-ci.yml):
1stages:
2 - quality
3 - test
4 - build
5
6# Cache node_modules
7cache:
8 paths:
9 - node_modules/
10
11# Code quality checks
12quality:
13 stage: quality
14 script:
15 - npm ci
16 - npm run lint
17 - npm run format:check
18 - npm run type-check
19 only:
20 - merge_requests
21 - main
22
23# Tests
24test:
25 stage: test
26 script:
27 - npm ci
28 - npm test -- --coverage
29 coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
30 artifacts:
31 reports:
32 coverage_report:
33 coverage_format: cobertura
34 path: coverage/cobertura-coverage.xml
35
36# Build verification
37build:
38 stage: build
39 script:
40 - npm ci
41 - npm run build
42 artifacts:
43 paths:
44 - dist/
45 expire_in: 1 weekPull Request Template (.github/pull_request_template.md):
1## Description
2<!-- Describe your changes -->
3
4## Type of Change
5- [ ] Bug fix
6- [ ] New feature
7- [ ] Breaking change
8- [ ] Documentation update
9
10## Checklist
11- [ ] Code follows project style guidelines
12- [ ] Self-review completed
13- [ ] Comments added for complex code
14- [ ] Documentation updated
15- [ ] No new warnings
16- [ ] Tests added/updated
17- [ ] All tests passing
18- [ ] Lint and format checks pass
19- [ ] TypeScript compilation successful
20
21## Quality Checks
22<!-- Automatically populated by CI/CD -->
23- ESLint: ✅ Passed
24- Prettier: ✅ Passed
25- TypeScript: ✅ Passed
26- Tests: ✅ 100% passing, 85% coverage
27- Build: ✅ SuccessfulTeam Workflow Best Practices:
1# 1. SHARED CONFIGURATION
2# Commit all config files to repository
3git add .eslintrc.json .prettierrc.json tsconfig.json
4git commit -m "chore: Add code quality configuration"
5
6# 2. EDITOR CONFIGURATION
7# .vscode/settings.json (committed to repo)
8{
9 "editor.formatOnSave": true,
10 "editor.codeActionsOnSave": {
11 "source.fixAll.eslint": true
12 },
13 "eslint.validate": [
14 "javascript",
15 "typescript"
16 ]
17}
18
19# 3. RECOMMENDED EXTENSIONS
20# .vscode/extensions.json (committed to repo)
21{
22 "recommendations": [
23 "dbaeumer.vscode-eslint",
24 "esbenp.prettier-vscode",
25 "ms-vscode.vscode-typescript-next"
26 ]
27}
28
29# 4. DOCUMENTATION
30# docs/CONTRIBUTING.md
31## Code Quality Standards
32
33### Before Committing
341. Run `npm run check` to verify all quality checks
352. Ensure all tests pass: `npm test`
363. Follow conventional commit format
37
38### Code Style
39- Use ESLint and Prettier (configured in repo)
40- Format on save should be enabled
41- Fix all linter warnings before committing
42
43### Pull Requests
44- All CI/CD checks must pass
45- Code review required
46- No merge until quality checks succeedMonitoring Code Quality:
1// Code coverage reports
2// jest.config.js
3module.exports = {
4 collectCoverage: true,
5 coverageReporters: ['text', 'lcov', 'html'],
6 coverageThreshold: {
7 global: {
8 branches: 80,
9 functions: 80,
10 lines: 80,
11 statements: 80
12 }
13 }
14};
15
16// SonarQube integration
17// sonar-project.properties
18sonar.projectKey=my-project
19sonar.sources=src
20sonar.tests=src
21sonar.test.inclusions=**/*.test.ts,**/*.spec.ts
22sonar.javascript.lcov.reportPaths=coverage/lcov.info
23sonar.exclusions=node_modules/**,dist/**,coverage/**
24
25// Quality gates
26// Fail build if:
27// - Coverage < 80%
28// - Any critical issues
29// - Code smells > 50
30// - Duplicated code > 3%Troubleshooting Common Issues:
1# Issue: ESLint and Prettier conflict
2# Solution: Use eslint-config-prettier
3npm install --save-dev eslint-config-prettier
4
5# .eslintrc.json - Add prettier last
6{
7 "extends": [
8 "eslint:recommended",
9 "prettier" // Disables ESLint formatting rules
10 ]
11}
12
13# Issue: TypeScript types not found
14# Solution: Install type definitions
15npm install --save-dev @types/node @types/react
16
17# Issue: Husky hooks not running
18# Solution: Reinstall husky
19rm -rf .husky
20npx husky install
21npx husky add .husky/pre-commit "npx lint-staged"
22
23# Issue: Lint-staged not formatting
24# Solution: Check file patterns
25{
26 "lint-staged": {
27 "*.{js,ts}": ["eslint --fix", "prettier --write"]
28 }
29}
30
31# Issue: CI/CD failing but local passing
32# Solution: Use same Node version
33# .nvmrc
3418.17.0
35
36# GitHub Actions
37- uses: actions/setup-node@v3
38 with:
39 node-version-file: '.nvmrc'Complete Quality Workflow:
1# Development
2npm install # Install deps
3npm run dev # Start dev server
4# → Code with auto-formatting on save
5# → See linter warnings in editor
6
7# Before commit
8npm run check # Run all checks locally
9npm test # Run tests
10
11# Commit
12git add .
13git commit -m "feat: Add feature"
14# → Pre-commit hook runs
15# → lint-staged formats and lints
16# → TypeScript checks types
17
18# Push
19git push
20# → Pre-push hook runs
21# → Full test suite
22# → Build check
23
24# Pull Request
25# → CI/CD runs all checks
26# → Code review
27# → Merge when approved and checks pass
28
29# Production
30# → Build with all optimizations
31# → Quality gates enforced
32# → Deploy only if all checks passYour progress is saved in this browser, on this device only.