Determine the sum of exported identifiers based on ESLint rules

Currently, I am facing a requirement in my JavaScript/TypeScript monorepo to ensure that each library maintains a minimal amount of exported identifiers.

Is there any existing eslint rule or package available that can keep track of the total number of exported symbols?

export * from './lib/foo' // 4 identifiers exported
export {x,y} from './lib/bar' // 2 identifiers exported
// For example, if there was a lint rule like "max-exports 5", this would trigger an error

I understand that implementing such a rule may pose difficulties in scenarios like dynamic exports, re-exporting from third-party packages, or custom resolvers...

Nevertheless, even an imperfect but practical implementation would be highly valuable. Despite conducting thorough searches on Google and npm, I have been unable to find any relevant solutions!

Answer №1

Given the specificity of your situation, it seems like creating a custom solution may be necessary. Thankfully, eslint offers options for developing custom rules.

In this scenario, where the number of exported members exceeds 1, targeting instances where the export is an object can be achieved. The implementation could resemble the following:

const MAX_NUM_EXPORTS = 2;

module.exports = {
  meta: {
    messages: {
      maxExports: `Modules can only contain up to ${MAX_NUM_EXPORTS} exports`
    }
  },
  create(context) {
    return {
      ExportDefaultDeclaration(node) {
        if (
            node.declaration.type === "ObjectExpression" 
            && node.declaration.properties.length > MAX_NUM_EXPORTS  
        ) {
          context.report({ node, messageId: 'maxExports'})
        }
      },
      ExportNamedDeclaration(node) {
        if (node.declaration.type === "VariableDeclaration") {
          node.declaration.declarations.forEach(dec => {
            if (
                dec.init.type === "ObjectExpression"
                && dec.init.properties.length > MAX_NUM_EXPORTS
            ) {
              context.report({ node, messageId: 'maxExports'})
            }
          })
        }
      }
    }
  }
}

The create method returns callbacks, and utilizing an AST Explorer helps identify which nodes in the tree to target for the rule execution within eslint. Particularly, focusing on the ExportNamedDeclaration node for standard variable exports and the ExportNamedDeclaration node for default exports.

After capturing these nodes, the script verifies if they are objects and if those objects exceed the defined limits in terms of properties. If so, an error message is triggered.

Further information on eslint custom rules can be found in the mentioned documentation.

To verify the effectiveness of these rules, leveraging the built-in eslint RuleTester class, paired with preferred testing frameworks, such as the Jest unit testing suite, is recommended.

Implementing the RuleTester class in a file named rule.test.js would appear similar to the example below:

const { RuleTester } = require('eslint');
const limitMaxExports = require('./rule');

const ruleTester = new RuleTester(
  {
     parserOptions: { 
       ecmaVersion: 6,
       sourceType: 'module'
     }
  }
);

const valid = JSON.stringify({
    a: 'this',
    b: 'is valid'
});

const invalid = JSON.stringify({
    a: 'this',
    b: 'has',
    c: 'too many'
});

ruleTester.run('limit-max-exports', limitMaxExports, {
  valid: [
    {
      code: `export default ${valid}`
    },
    {
      code: `export const blah = ${valid}`
    }
  ],
  invalid: [
    {
      code: `export default ${invalid}`,
      errors: [{messageId: 'maxExports'}]
    },
    {
      code: `export const invalid = ${invalid}`,
      errors: [{messageId: 'maxExports'}]
    }
  ]
});

Upon testing with jest, successful completion of all tests will be indicated:

https://i.stack.imgur.com/Virqy.png

Note that this demonstration is tailored for es6 modules exclusively, as specified in the RuleTester instantiation parameter object.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Show Timing on the Y-Axis - Bubble Graph

Recently, I stumbled upon the Bubble Chart feature in ng2-charts. I am trying to display data based on time on the Y-axis and values on the X-axis. My dataset consists of x:[10,35,60], y:["7.00 AM"], with r having the same value as x. However, the sample d ...

Utilizing props in styled-components: A beginner's guide

I am trying to pass a URL to a component so that I can use it as the background image of the component. Additionally, I need to check if the URL is empty. Component: <BannerImg/> CSS (styled): `const BannerImg = styled.img` background-image: url( ...

Testing the submission event on a reactive form in Angular

Scenario In my component, I have a basic form implemented using reactive forms in Angular. My objective is to test the submission event of this form to ensure that the appropriate method is executed. The Issue at Hand I am encountering challenges in tri ...

Understanding the fundamentals of TypeScript annotation and node package management

As a newcomer to Typescript, I have grasped the basics but find myself becoming a bit bewildered when it comes to best practices for handling node packages, annotations, and defining types within those packages in my projects. Do I really need to annotate ...

When utilizing the dispatch function with UseReducer, an unexpected error is triggered: Anticipated 0 arguments were provided,

Having trouble finding a relevant answer, the only one I came across was related to Redux directly. So here's my question that might be obvious to some of you. In my code, everything appears to be correct but I'm facing an error that says: Expect ...

Tips on Configuring the Attributes of a TypeScript Object in Angular 2

A TypeScript class called Atom is defined as follows: export class Atom { public text: String; public image: boolean; public equation: boolean; } To create an object of type Atom class and set its properties, the following steps are used: atom: ...

In Typescript, is there a specific type for images encoded in base64 format?

As a newbie to Typescript, I am diligently typing everything precisely as part of my learning journey. A property called lqip (low quality image placeholder) is being pulled from a CMS and should be a base64 encoded image. It's clearly a string, but ...

How can I turn off the eslint overlay in the newest version of Vue?

After upgrading to the latest version of Vue or its dependencies, I noticed that the code in my vue.config.js no longer works: chainWebpack: config => { // disable eslint nag screen when building for different environments if (!isProduction) config. ...

Unable to retrieve selected value from Flowbite-React Datepicker due to malfunctioning props change event

I am encountering an issue with extracting the selected value from the Datepicker component in the flowbite-react library while using it with NextJS. The component is being displayed correctly. I attempted the code below, but it does not return anyth ...

What is the best way to pass dynamic values to a service constructor from a component?

After days of attempting to grasp 'the Angular paradigm', I still find myself struggling to understand something about services that are not singletons. It seems impossible for me to pass a runtime-determined value to a service constructor, as I ...

Using TypeScript to validate the API response against specific types

I'm intrigued by the scenario where you expect a specific data type as a response from fetch / Axios / etc, but receive a different type instead. Is there a way to identify this discrepancy? interface HttpResponse<T> extends Response { parsed ...

Utilizing the URL path name for data retrieval in Next.js 14 - A step-by-step guide

I'm currently developing a blog using AWS Amplify Gen 2 and GraphQL for a Next.js 14 project with TypeScript. As part of my application, I need to fetch specific data based on the URL path name. Here's how I've approached it: My approach in ...

The srcSet functionality in the Image component seems to be malfunctioning in the next.js framework, as it is failing to display

Check out my Next.js code snippet below: import React from "react"; import style from "@/styles/Home.module.css"; import Image from "next/image"; function index() { return ( <> <div className="contai ...

Tips for ensuring the correct typing of a "handler" search for a "dispatcher" style function

Imagine having a structure like this: type TInfoGeneric<TType extends string, TValue> = { valueType: TType, value: TValue, // Correspond to valueType } To prevent redundancy, a type map is created to list the potential valueType and associate i ...

How to Retrieve an Array from a Promise Using Angular 4 and Typescript

I am encountering difficulties when trying to store data from a returned promise. To verify that the desired value has been returned, I log it in this manner: private fetchData() { this._movieFranchiseService.getHighestGrossingFilmFranchises() ...

Error: Unable to locate namespace 'google' in TypeScript

I am currently working on an angular-cli project. ~root~/src/typings.json { "globalDevDependencies": { "angular-protractor": "registry:dt/angular-protractor#1.5.0+20160425143459", "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", "sele ...

How to Modify a Module that was Imported in Typescript

Apologies for my inexperience in this language. I've been working on a custom Discord bot and encountered a problem. I implemented the feature to load commands dynamically from a folder, with each command as a module. However, when trying to create a ...

Tips for preserving updates following modifications in Angular 5/6?

I'm currently working on enhancing the account information page by implementing a feature that allows users to edit and save their details, such as their name. However, I am encountering an issue where the old name persists after making changes. Below ...

TypeScript: Defining a custom object type using an array of objects

Is there a way to dynamically generate an object based on the name values in an array of objects? interface TypeA { name: string; value: number; } interface TypeB { [key: string]: { value: any }; } // How can we create an OutputType without hard ...

Cannot execute npm packages installed globally on Windows 10 machine

After installing typescript and nodemon on my Windows 10 machine using the typical npm install -g [package-name] command, I encountered a problem. When attempting to run them through the terminal, an application selector window would open prompting me to c ...