How can eslint be used to enforce a particular named export?

Is there a way to use eslint to make it mandatory for JavaScript/TypeScript files to have a named export of a specific name?

For instance, in the src/pages folder, I want all files to necessitate an export named config:

Example of incorrect usage

src/pages/index.js

export const Page = () => {}

// Error: no export named `config`

Correct example

src/pages/index.js

export const Page = () => {}

export const config = {}

Does anyone know if this is currently achievable? I've explored various eslint rules and also the eslint-plugin-import, but haven't come across anything that fits this specific requirement.

In addition, does anyone have any suggestions on how one could also enforce a particular type for the config export in TypeScript?

Answer №1

Though I may be arriving a bit tardy, here is some sample code that has been thoroughly tested for functionality. I recently formulated a similar request for requiring an exported variable of type StorybookDocumentation with the same objective in mind.

The key idea behind this approach involves creating a custom rule that:

  1. Begins from the Program root node and meticulously scans all tokens to identify export-related tokens (in my scenario, I opted for named exports, but by tweaking the logic slightly, default exports can also be targeted).
  2. Once the selection narrows down to only exported variables and their types, they are grouped into an array of [identifier, type] pairs.
  3. Subsequently, it is ensured that at least one of these pairs corresponds to an exported variable of the correct name and type; otherwise, an issue is raised.
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';


const arePriorTokensExportConst = (index: number, tokens: TSESTree.Token[]) => {
    const previousTokenIsConst = tokens[index - 1]?.value === 'const';
    const previousPreviousTokenIsExport = tokens[index - 2?.value === 'export';
    const isNamedExport = previousTokenIsConst && previousPreviousTokenIsExport;
    return isNamedExport;
};
export const requireStorybookDocumentation = ESLintUtils.RuleCreator.withoutDocs({
    create(context) {
        return {
            Program(program) {
                const namedExports = context.sourceCode.tokensAndComments
                    .filter((token, currentIndex, tokens) => {
                        if (token?.type !== 'Identifier') {
                            return false;
                        }
                        const isNamedExport = arePriorTokensExportConst(currentIndex, tokens);
                        const isTypeForNamedExport =
                            tokens[currentIndex - 1?.value === ':' &&
                            arePriorTokensExportConst(currentIndex - 2, tokens);
                        return isNamedExport || isTypeForNamedExport;
                    })
                    .reduce<TSESTree.Token[][]>((acc, cur, index) => {
                        if (index % 2 === 0) {
                            acc.push([cur]);
                            return acc;
                        }
                        acc[acc.length - 1].push(cur);
                        return acc;
                    }, []);

                if (
                    !namedExports.some(namedExport => {
                        const [identifier, identifierType] = namedExport;
                        return (
                            identifierType?.value === 'StorybookDocumentation' && identifier?.value === 'Documentation'
                        );
                    })
                ) {
                    context.report({
                        messageId: 'require-storybook-documentation',
                        node: program,
                    });
                }
            },
        };
    },
    meta: {
        docs: {
            description: 'Function Component files should export a Storybook documentation configuration',
        },
        messages: {
            'require-storybook-documentation': 'Define and export a StorybookDocumentation object named Documentation',
        },
        type: 'suggestion',
        schema: [],
    },
    defaultOptions: [],
});

export default {
    'require-storybook-documentation': requireStorybookDocumentation,
}cater to Record<string, ESLintUtils.RuleModule<any, any>>;

In addition, comprehensive tests have been included to confirm its expected behavior


import { RuleTester } from '@typescript-eslint/rule-tester';
import { describe, after, it } from 'mocha';
import { requireStorybookDocumentation } from '../rules';
RuleTester.afterAll = after;
RuleTester.describe = describe;
RuleTester.it = it;
RuleTester.itOnly = it.only;

const ruleTester = new RuleTester({
    parser: '@typescript-eslint/parser',
    parserOptions: {
        project: './tsconfig.json',
        ecmaFeatures: { jsx: true },
        tsconfigRootDir: __dirname,
    },
});

ruleTester.run('my-rule', requireStorybookDocumentation, {
    valid: [
        {
            code: `
    import { FunctionComponent } from 'react';
    import { StoryObj, Meta } from '@storybook/react';

    type StorybookDocumentation<TComponent extends FunctionComponent, TDefinedStories> = {
        Meta: Meta<TComponent>,
        Stories: Record<TDefinedStories, StoryObj<TComponent>>
    };

    export const MyComponent: FunctionComponent = () => <h1>Hello World!</h1>;
    export const Documentation: StorybookDocumentation<typeof MyComponent, 'Primary'>  = {
        Meta: {
            component: MyComponent,
        },
        Stories: {
            Primary: {}
        }
    };
`,
        },
    ],
    invalid: [
        {
            code: `
        import { FunctionComponent } from 'react';
        export const MyComponent: FunctionComponent = () => <h1>Hello World!</h1>;
    `,
            errors: [{ messageId: 'require-storybook-documentation' }],
        },
    ],
});

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

Looking to locate or track duplicate values within a multi-dimensional array?

In a multidimensional array, I am looking to detect and count duplicates. If duplicates are found, an alert should be triggered. Arr =[[2,"sk"],[3,"df"],[7,"uz"],[3,"df"],[7,"gh"]] Suggestions: One way to ...

Troubleshooting SocketIO connection problems and optimizing performance through clustering

I am facing an issue with my NodeJS app that I am trying to deploy on Heroku. Here is a snippet from the index.js file: Server (port 3030) const http = require('http'); const os = require('os'); const express = require('express&a ...

Issues with showing data in Angular Material tables

I recently deployed my Angular Application on a server and encountered an issue with the Angular Material table. Despite data being present in the logs, it does not display on the table. This problem only occurs in production, as everything works perfectl ...

Creating a dummy localhost server in Protractor to act as a substitute for a separate localhost server running on a distinct port

Test Scenario: In my testing scenario, I am trying to pass a token to my Chromium browser as a cookie. However, I am facing an issue where I cannot add it to the params because I need it to work with GET requests that are not localhost. When I make use o ...

The command 'prefix -g' is encountering an error and is not recognized as a valid command during the installation process of Angular

Microsoft Windows [Version 10.0.16299.309] (c) 2017 Microsoft Corporation. All rights reserved. C:\Users\Futluz>cd desktop/angular-cli-master/angular-cli-master C:\Users\Futluz\Desktop\angular-cli-master\angular-cli-mas ...

transferring data from a web page to a php script seamlessly without navigating away

I've already spent a significant amount of time searching on Stack Overflow and Google, but I haven't found a solution that works for me. My issue is that I have an HTML form and I need to send the data to PHP without redirecting or leaving the ...

Guide to selecting an element with a combination of text and a random number using Selenium with JavaScript

<a id="Message4217" class="btn-sm btn-danger Message" data-id="4217"><span class="icon-adjustment icon-trash"></span> Delete</a> The objective is to remove a message base ...

Is there a better method to accomplish this task in a more effective manner?

Is there a more efficient way to achieve this code with fewer lines of code? I'm looking for a solution that avoids repetition and makes it easier to manage, especially since I plan on creating multiple instances of this. Performance is a key consider ...

When a function is called, retrieve a specific number of elements from an array using the slice method in

If I have an array of 15 objects, but I am only displaying 5 on the browser using this code: const displayedArray = array.slice(0,4) I also have a function that will load another 5 objects each time a user scrolls down. displayedArray.concat(array.slice ...

Encountering a mixed content error in Internet Explorer 8 due to the Nivo slider jQuery?

I am encountering an issue with the Nivo jQuery slider on my HTTPS website, as it appears to be generating a mixed content error in Internet Explorer 8. Despite posting on the Dev7 Studios forum and conducting extensive research on the IE 8 mixed content ...

Is it possible to update the event parameters with every click?

Is there a way to dynamically add a Select box for selecting a condition each time the "add" button is clicked? For example, when the add button is clicked, I would like the following elements to be continuously added: https://i.stack.imgur.com/6bad2.png ...

The integration of Laravel (Homestead) Sanctum is malfunctioning when combined with a standalone Vue application

After running the command php artisan serve my Laravel application successfully resolves on localhost:8000. I have configured Laravel Sanctum as follows: SESSION_DRIVER=cookie SESSION_DOMAIN=localhost SANCTUM_STATEFUL_DOMAINS=localhost:8080 As for m ...

Leverage server-side data processing capabilities in NuxtJS

I am currently in the process of creating a session cookie for my user. To do this, I send a request to my backend API with the hope of receiving a token in return. Once I have obtained this token, I need to store it in a cookie to establish the user' ...

What causes a React Ref callback to be invoked multiple times during the initial loading of a page?

To find more information, please visit this URL within the React DOCS. You can also access a version of this code here. I acknowledge that it is recommended to use the useCallback hook in a Functional React Component to create a ref callback according to ...

Transferring binary fragments to Node.js for assembly into a complete file. Creating a file

Hey there, I'm in a bit of a bind. I'm trying to send file chunks using multiple XMLHttpRequest requests and then receive these parts in Node.js to reconstruct the original file from the binary data. The issue I'm facing is that the final f ...

Can type information be incorporated during compilation?

Consider the code snippet below: function addProperties(keys: String[]): Object { // For illustration purposes, this is a specific return return { firstProperty: "first_value", secondProperty: "second_value" }; } export defaul ...

AngularJS form submission with and without page refresh

Looking to implement an AngularJS form directive where, if on /home page, redirect to /search/term and if already on /search page, submit without refreshing the page by simply changing the location. I am able to do both separately, but struggling to writ ...

Utilizing server-side cookies in next.js and nest.js for efficient data storage

I have been working on a small application using Next.js and Nest.js. One of the functionalities I implemented is a /login call in my client, which expects an HttpOnly Cookie from the server in response. Upon receiving a successful response, the user shoul ...

Moving various divisions through Javascript by clicking various buttons although sharing the same identifier

I am working with the script below: $(document).ready(function() { $('.expandButton').click(function() { $('.expandableSection').toggle("slide"); }); }); My goal is to apply this script to multiple sections. However, ...

Storing TypeScript functions as object properties within Angular 6

I am working on creating a simplified abstraction using Google charts. I have implemented a chartservice that will act as the abstraction layer, providing options and data-source while handling the rest (data retrieved from a REST API). Below is the exist ...