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

Tips for presenting a quiz in a random order and preventing duplicate questions in a React Native app

Hello everyone, I am working on creating a Quiz app in React Native that displays questions randomly without any duplication. So far, I have managed to display the Quiz questions randomly, but I'm stuck on preventing duplicates. This is a new learning ...

Please click twice in order to log in to Angular 16

Whenever I attempt to log in, I face the issue of having to click twice. The first click does not work, but the second one does. Additionally, an error message pops up: TypeError: Cannot read properties of undefined (reading 'name'). I am unsure ...

Firebase - Accessing data for a specific item

Apologies for the lengthy question. I have a collection of events that I retrieve like this: export class HomePageComponent implements OnInit { events: FirebaseListObservable<EventModel[]>; constructor( private authService: AuthService, ...

`How to prevent Query parameters from being lost upon reloading in Nextjs Router while maintaining a clean URL structure?`

My challenge lies in sending data via router.push to a page with dynamic room id (src/pages/editor/[roomid].tsx) in Next.js. I want the URL to stay clean so users can easily edit their username in the URL if needed. When initially loaded, router.query suc ...

Here's a way to resolve the issue: ReactDOM.render() - TS2345 error: Cannot assign type '() => Element' to type 'ReactElement' in the argument

After tackling React-Router with Typescript, I encountered a typing issue that has me perplexed. Prior to this, I was using an older version of React and React-Router. But now, after updating to the latest builds using yarn, I'm facing this hurdle. ...

Can you explain how to utilize multiple spread props within a React component?

I'm currently working in TypeScript and I have a situation where I need to pass two objects as props to my React component. Through my research, I found out that I can do this by writing: <MyComponent {...obj1} {...obj2} /> However, I'm fa ...

Guide to creating two-way data binding using ngModel for custom input elements like radio buttons

I am currently facing an issue with implementing a custom radio button element in Angular. Below is the code snippet for the markup I want to make functional within the parent component: <form> <my-radio [(ngModel)]="radioBoundProperty" value= ...

"Introducing the new Next.Js 14 sidebar featuring a sleek hamburger menu

I am in the process of developing a chat application. Currently, I have a sidebar that displays existing conversations and I would like to make it responsive by implementing open and close functionalities for mobile devices. Here is the code snippet for m ...

What is the best approach to validating GraphQL query variables while utilizing Mock Service Worker?

When simulating a graphql query with a mock service worker (MSW), we need to verify that the variables passed to the query contain specific values. This involves more than just type validation using typescript typings. In our setup, we utilize jest along ...

What is the best way to loop through the keys of a generic object in TypeScript?

When I come across a large object of unknown size, my usual approach is to iterate over it. In the past, I've used generators and custom Symbol.iterator functions to make these objects iterable with a for..of loop. However, in the ever-evolving world ...

The slides in Swiperjs are having trouble moving smoothly

I am experiencing some challenges with swiperjs where the slides are not snapping to the next slide and I am unable to fetch the active index from them. Below is a snippet of my code where I am using typescript, swiperjs version 6.84, and the Ionic frame ...

Enclose this within Stencil.js components

Is there a more efficient way to utilize a nested "this" in a Stencil.js component? Currently, I find myself following this approach: render() { let thisNested = this; return <Host> {this.images ? this.imagesArray.map(fu ...

What are the distinctions between generic and discriminated types?

Hi there, I've been thinking of an idea but I'm not sure how to implement it or if it's even possible. Is there a way to create a type SomeType where the first property can be any value from the set T, but the second property cannot be the ...

Loading a large quantity of items into state in React Context using Typescript

I am currently working on a React context where I need to bulk load items into the state rather than loading one item at a time using a reducer. The issue lies in the Provider initialization section of the code, specifically in handling the api fetch call ...

Explicit final argument in TypeScript

Is it feasible to define a function in TypeScript 2.7.2 and above with variable parameters, but ensuring that the final parameter has a specific type? I am attempting to craft an ambient TypeScript declaration for a JavaScript library that utilizes functi ...

What are some ways to detect TypeScript type errors in the data of a Vue component?

Recently, I delved into Typescript development using Nuxt-ts and Vue 2. My goal was to steer clear of class-style components so I opted for the following approach. I created my Interfaces in a folder named /types. Whenever I needed to declare a type in a ...

Migrating image information from Angular version 14 to Asp.Net Core 6.0 Rest Api

When transferring product model data from Angular to a REST API using FormData, there is an images array included in the product data. Upon receiving this product in the REST API, the images data is accessed using Request.Form.Files. The images are then se ...

Plugin for managing network connectivity in Ionic framework

In order to check if internet and id connection are available, I need to make a server request. I have implemented the Ionic Native Network Plugin following their official documentation. Here is my code snippet: import { Component } from '@angular/c ...

Develop a type definition utilizing dotted paths from a recursive object model

Working with TypeScript, I am dealing with a nested object structure of functions defined as: type CallbackFn = (args: any) => any type CallbackObj = { [key: string]: CallbackFn | CallbackObj } const callbacks = { foo: function(args: { x: num }): st ...

Specifying the data structure of a complex nested Map in TypeScript

Struggling to create a deeply nested (recursive) Map in Typescript generically. My attempt is to convert the provided Javascript example to Typescript: const map1 = new Map([ ['key1', 'value1'] ]) const map2 = new Map([ ['keyA& ...