Fix the "can't find custom rule directory: tslint-microsoft-contrib" error in SPFx projects

Learn how to resolve the error: "[tslint] Failed to load ../tslint.json: Could not find custom rule directory: tslint-microsoft-contrib"

By Last Updated: December 7, 2024 5 minutes read

Recently, when I was rebuilding all the projects in my Mastering the SharePoint Framework course to the latest version of the SharePoint Framework (SPFx) v1.12.1, I kept running into a random error. It didn’t happen all the time which made it not just annoying, but a pain trying to figure out the root cause.

Error - [tslint] Failed to load tslint.json: Could not find custom rule directory: tslint-microsoft-contrib

Error - [tslint] Failed to load tslint.json: Could not find custom rule directory: tslint-microsoft-contrib

It appears it’s just a random oddity that happens on occasion without a good explanation as to why. But the good news is that it’s easily fixed. In this post, I’ll explain the problem as well as how to fix it.

What’s the problem?

Occasionally you might get the following error when creating a new SPFx project or installing the dependencies of a project you collected. This shows up after you’ve installed all npm package dependencies and run your first build:

Error - [tslint] Failed to load {{project-path}}/tslint.json:
Could not find custom rule directory: tslint-microsoft-contrib

TL;DR

No time to read on about what’s going on and just want the fix? No worries… just install a missing npm package as a dev dependency and rebuild the project:

npm install tslint-microsoft-contrib --save-dev --save-exact

Unpacking the issue

Let’s take a look at what’s going on here.

The SharePoint Framework is still using TSLint, an extensible linter for TypeScript, even though it’s been deprecated for nearly 2.5 years. In addition to the default rules, Microsoft created some of their own custom rules. For instance, the no-cookies rule keeps developers from using the document.cookie API. These rules are defined in the npm package tslint-microsoft-contrib.

In default SPFx projects, Microsoft defines TSLint rules they believe every project should follow. This is done by adding the tslint.json file to your projects.

TSLint rules shouldn’t be included by default to new SPFx projects

Personally I disagree with the inclusion of this file… who are they to tell me how to code or what my company’s coding standards should be?

I tried, way back in July 2017 (see: sharepoint/sp-dev-docs#653) in the early days of SPFx, to get them to stop including this file, but as you can see from the discussion in that issue, I failed.

At least there’s a way to disable TSLint, but that means your workflow is always: create project > edit project to disable tslint.

If you take a look at the tslint.json file, you’ll see it extends the rules included with TSLint by referencing SharePoint-specific TSLint rules. These are found in the @microsoft/sp-tslint-rules npm package:

{
  "extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
  "rules": { .. }
}

Let’s follow the breadcrumbs…

Locate and open the base-tslint.json file referenced in our project’s tslint.json file. Notice it has a single property defining the Microsoft custom rules directory… it’s pointing to that npm package tslint-microsoft-contrib!

{
  "$schema": "http://json.schemastore.org/tslint",
  "rulesDirectory": ["tslint-microsoft-contrib", "./lib"]
}

Fantastic… now we can see how this is making it into the TSLint process.

But how is it getting into our project? That would be done by referencing it as a dependency or devDependency in a package’s package.json file. You’d logically expect to see this as a dependency of the @microsoft/sp-tslint-rules package… right? That’s the one that’s referencing it so it expects it to be there.

Now you’ll see where things start to break down. Take a look at the package.json file for the @microsoft/sp-tslint-rules:

{
  "name": "@microsoft/sp-tslint-rules",
  ..
  "dependencies": {
    "tslint": "~6.1.3",
    "tsutils": "~2.11.2"
  },
  "devDependencies": {
    "@microsoft/rush-stack-compiler-3.7": "0.6.38",
    "@ms/internal-heft-plugins": "0.3.1",
    "@ms/run-after-build-plugin": "0.1.0",
    "@ms/sp-internal-node-build-rig": "0.1.0",
    "@rushstack/heft": "0.24.1",
    "@rushstack/node-core-library": "3.35.2",
    "typescript": "~3.7.2"
  }
 ..
}

Notice anything missing? Yup… tslint-microsoft-contrib is a no-show.

But that doesn’t answer the question…

How does the npm package tslint-microsoft-contrib get into the project’s dependency tree?

Let’s dig further and open the package-lock.json file in the root of our project. That contains a manifest of the entire dependency tree. Search for the string tslint-microsoft-contrib. You’re looking for an instance of it in another package’s dependencies. In the project I’m playing around with, an SPFx v1.12.1 project that’s been updated to use TypeScript v3.9, I found this:

{
  ..
  "packages": { .. },
  "dependencies": {
    ..
    "node_modules/@microsoft/rush-stack-compiler-3.9": {
      "version": "0.4.47",
      ..
      "dependencies": {
        ..
        "tslint": "~5.20.1",
        "tslint-microsoft-contrib": "~6.2.0",
        "typescript": "~3.9.7"
      }
    }
  }
}

There it is… it’s a dependency on the npm package @microsoft/rush-stack-compiler-3.9.

So now we know how it’s getting installed on our project dependency tree. That doesn’t explain why it’s not getting installed at times and triggering the error I mentioned in the introduction of this post… we’ll just chalk that up to some weird circumstance.

Parting thoughts

Still… exposes something that’s a bit disappointing.

It’s a generally accepted practice that if you need something, you specify it as a dependency. Clearly the npm package @microsoft/sp-tslint-rules needs it, so why isn’t it listed as a dependency?

“Oh, it doesn’t matter because something it depends on needs it.”

While true, what would happen if the downstream packages decided they didn’t want to include it? It’s not their responsibility to find everyone who’s consuming them to say “hey, if you expected this, you should go install it yourself.”

From my point of view, the @microsoft/sp-tslint-rules should have the tslint-microsoft-contrib package listed as a dependency.