SonarLint: Setup and Best Practices for Clean Code

CodeStax.Ai
8 min readJul 10, 2024

--

In the world of software development ensuring the code quality is really important. Good code quality not only plays a vital role in minimizing errors but also it helps in enhancing the clarity and comprehensibility of the code for fellow developers. With help of this article, I am going to explain about sonarlint which will help us in following best Code practices and standards. After reading this article you will get a idea about setting up sonarlint and outline common practices to follow best practices for code smells.

What is SonarLint?

SonarLint is a free and open source IDE which was brought by Sonar. It is like a spellchecker which offers quick fixes and also support rich contextual educational guidance. By detecting these issues SonarLint enables to address them promptly during the coding process ultimately saving our time and energy in the long run.

Steps to install SonarLint in VS code

  1. Open VS Code, navigate to the Extensions tab, and search for ‘SonarLint.’
  2. Now Click on Install button.
  3. After the installation is complete, Reload the IDE to restart VS code.

NOTE: Sonarlint currently support Node version greater than 18.18. You may need to update your Node.js version accordingly.

For changing the Node version do the following steps.

  1. Download and install nvm
  2. Check the available Node.js versions using nvm ls
  3. Install the desired Node.js version
nvm install 18 // or greater than 18.17.0

4. Use the installed Node.js version

nvm use 18 // use your desired version, greater than 18.17.0

5. Set a default Node.js version: If you want to set a default Node.js version that will be used in every new terminal session, run:-

nvm alias default 18 // your desired version

6. After following steps check Node.js version:

node -v
// This will display the version of Node.js that you have set.

How it works:

Simply open any source file, start coding, and you will start seeing issues reported by SonarLint. Issues are highlighted in your code and also listed in the ‘Problems’ panel.

Here you can see:

When sonarlint is configured it will start giving warning as you open the files. Here you can see the problems that are addressed by sonarlint.
You can access the detailed rule description directly from your editor, using the provided contextual menu.

after opening this it will give full context about Why is it issue? How to fix etc.

Common Practices to Avoid Code Smells:

  1. Using Optional Chaining (?.):

Issue Description:

  • The . operator can cause issues if the chain is long and it's not safe, leading to runtime errors if an object in the chain is null or undefined.

Best Practice:

  • Use the optional chaining operator (?.) to safely access deeply nested properties without risking a runtime error.

With the optional chaining operator (?.), however, you don't have to explicitly test and short-circuit based on the state of responseData and responseData.Items[0] before trying to access responseData.Items[0]. Definitions, just directly access responseData?.Items[0]?.Definitions.

By using the ?. operator instead of just ., JavaScript knows to implicitly check to be sure responseData and responseData.Items[0] is not null or undefined before attempting to access responseData?.Items[0]?.Definitions, the expression automatically short-circuits, returning undefined.

NOTE: Optional chaining cannot be used on a non-declared root object, but can be used with a root object with value undefined.

undeclaredVar?.prop; // ReferenceError: undeclaredVar is not defined

2. Using Object.hasOwn()

Issue Description:

  • Object.hasOwnProperty can lead to runtime errors if obj.prototype is null and obj.hasOwnProperty is undefined.

Imagine we have an object structure as follows:

let roleResp = {
data: {
Items: [{
Definitions: {
access: Object.create(null) // an object with a null prototype
}
}]
}
};
let role = "admin";

In this case, roleResp.data.Items[0].Definitions.access is an object created with a null prototype. This means it does not inherit any properties or methods, including hasOwnProperty.

Using Object.prototype.hasOwnProperty.call would look like this:

if (Object.prototype.hasOwnProperty.call(roleResp.data.Items[0].Definitions.access, role)) {
console.log("Role exists in access definitions.");
} else {
console.log("Role does not exist in access definitions.");
}

If you run this code, it works because Object.prototype.hasOwnProperty.call uses the hasOwnProperty method from Object.prototype, which doesn't depend on the prototype of the target object (roleResp.data.Items[0].Definitions.access).

However, if someone mistakenly uses obj.hasOwnProperty directly on an object with a null prototype, it will throw an error because hasOwnProperty does not exist on roleResp.data.Items[0].Definitions.access:

if (roleResp.data.Items[0].Definitions.access.hasOwnProperty(role)) {
// This line throws a TypeError: roleResp.data.Items[0].Definitions.access.hasOwnProperty is not a function
console.log("Role exists in access definitions.");
} else {
console.log("Role does not exist in access definitions.");
}

Best Practice:

  • Use Object.hasOwn() introduced in ES2022 for a safer and more concise property check.
  • Now, let’s use the new Object.hasOwn method:
if (Object.hasOwn(roleResp.data.Items[0].Definitions.access, role)) {
console.log("Role exists in access definitions.");
} else {
console.log("Role does not exist in access definitions.");
}

This approach is safer and avoids potential errors, as Object.hasOwn does not depend on the prototype of the target object. It directly checks for the property on the object itself, making it robust against objects with null prototypes.

So, the replacement is straightforward:

// Noncompliant
if (Object.prototype.hasOwnProperty.call(roleResp.data.Items[0].Definitions.access, role)) {
console.log("Role exists in access definitions.");
}
// Compliant
if (Object.hasOwn(roleResp.data.Items[0].Definitions.access, role)) {
console.log("Role exists in access definitions.");
}

3. Simplifying Boolean Checks:

Issue Description:

  • Redundant checks for boolean variables, such as variable == false, can be simplified.

Best Practice:

  • Use the ! operator for a cleaner and more readable boolean check.

So the above code can be simplified as: !applicationDetails.status

4. Using for-of Loop:

Issue Description:

  • Traditional for loops are less readable and more error-prone compared to for-of loops.
for (let i = 0; i < this.rows.length; ++i) {
if (!this.rows[i].selected) {
this.selectAllChecked = false;
break;
}
}

Best Practice:

  • Use for-of loops for iterating over arrays and other iterable objects.
for (let row of this.rows) {
if (!row.selected) {
this.selectAllChecked = false;
break;
}
}

Note: This not only improves readability but also aligns with best practices for writing maintainable JavaScript.

5. Using Parameter Destructuring

Issue Description:

  • Functions with more than 7 parameters can be confusing and hard to manage.

Best Practice:

  • Pass parameters as an object and destructure them inside the function for better readability and maintainability.

See here for Information:

and check where the function is called change their also:

6. Reducing Cognitive Complexity:

Issue Description:

  • Functions with high cognitive complexity (more than 15) are difficult to understand and maintain.

Best Practice:

  • Refactor complex functions by dividing them into smaller, more manageable sub-modules.

Let’s understand this with a following example:

Consider a function that processes a list of orders, applying various discounts and taxes, and then generates an invoice. This function might have a high cognitive complexity due to multiple nested conditionals and loops.

## High cognitive complexity function
function processOrders(orders) {
let total = 0;
for (let i = 0; i < orders.length; i++) {
let order = orders[i];
let discount = 0;
// Calculate discount
if (order.customerType === 'VIP') {
if (order.total > 1000) {
discount = order.total * 0.1;
} else {
discount = order.total * 0.05;
}
} else if (order.customerType === 'Regular') {
if (order.total > 500) {
discount = order.total * 0.05;
}
}
// Apply tax
let tax = 0;
if (order.location === 'CA') {
tax = (order.total - discount) * 0.08;
} else if (order.location === 'NY') {
tax = (order.total - discount) * 0.07;
}
// Calculate final amount
let finalAmount = order.total - discount + tax;
total += finalAmount;
// Generate invoice
generateInvoice(order, finalAmount);
}
return total;
}
function generateInvoice(order, finalAmount) {
console.log(`Invoice for ${order.id}: ${finalAmount}`);
}

After Refactoring into modules it will Reduce Cognitive Complexity:

function processOrders(orders) {
let total = 0;
for (let order of orders) {
let discount = calculateDiscount(order);
let tax = calculateTax(order, discount);
let finalAmount = calculateFinalAmount(order, discount, tax);
total += finalAmount;
generateInvoice(order, finalAmount);
}
return total;
}
function calculateDiscount(order) {
if (order.customerType === 'VIP') {
return order.total > 1000 ? order.total * 0.1 : order.total * 0.05;
} else if (order.customerType === 'Regular' && order.total > 500) {
return order.total * 0.05;
}
return 0;
}
function calculateTax(order, discount) {
if (order.location === 'CA') {
return (order.total - discount) * 0.08;
} else if (order.location === 'NY') {
return (order.total - discount) * 0.07;
}
return 0;
}
function calculateFinalAmount(order, discount, tax) {
return order.total - discount + tax;
}
function generateInvoice(order, finalAmount) {
console.log(`Invoice for ${order.id}: ${finalAmount}`);
}

Explanation:

  1. Separation of Concerns:
  • The processOrders function has been divided into four smaller functions: calculateDiscount, calculateTax, calculateFinalAmount, and generateInvoice.
  • Each function is handling a different parts of the order processing, which is helping to make the logic easier to follow.

2. Improved Readability:

  • The processOrders function now look more like a sequence of high-level steps improving overall readability.

3. Reduced Cognitive Complexity:

  • The cognitive complexity of the original processOrders function is reduced by dividing it into modules.

Conclusion:

Sonarlint is like a helping tool which will help to write effective code. it’s like having a teacher right there with you, which will point out the ways to improve your code. But that’s not all! SonarLint is also like a security guard for your code. It watches out for any potential weak points that could let bad things happen. By learning from SonarLint, you can write better code and become more aware of keeping your code safe and secure.

About the Author:

Sidhant Singh is a Software Development Engineer (SDE1) at Codestax.ai, With a passion for technology, he enjoys sharing his insights on innovation, productivity and personal development.

About CodeStax.Ai

At CodeStax.AI, we stand at the nexus of innovation and enterprise solutions, offering technology partnerships that empower businesses to drive efficiency, innovation, and growth, harnessing the transformative power of no-code platforms and advanced AI integrations.

But what is the real magic? It’s our tech tribe behind the scenes. If you have a knack for innovation and a passion for redefining the norm, we have the perfect tech playground for you. CodeStax. Ai offers more than a job — it’s a journey into the very heart of what’s next. Join us and be part of the revolution that’s redefining the enterprise tech landscape.

--

--

CodeStax.Ai

Tech tales from our powerhouse Software Engineering team!