Search code examples
eslinttypescript-eslinteslint-rule-schema

Custom ESLint rule for multiple imports


I have Library A and Library B. I am trying to make a custom ESLint warning, so that Library A shows a deprecated message BUT ONLY in the instance that Library B is also present in the file.

module.exports = {
  meta: {
    messages: {
      useComponent: 'Do not use these two imports together',
    },
  },
  create(context) {
    return {
      ImportDeclaration(node) {
        if (node.source.value.includes(`Library A`)) {
          // How to check for the two imports occurring together?
          // I only want to add this message if Library B is also included
          context.report({
            node,
            messageId: 'useComponent',
          });
        }
      },
    };
  },
};

I've tried context.getText(), context.source.getText(), etc. Every variation I can find to try, but those all end up as undefined.


Solution

  • I would collect the relevant import nodes first, and check at the end of the file if any invalid combination is used.

    To do so, you can use state variables inside create. Variables declared in the create function of a rule have the scope of a file being linted - because create is called once for each file - so they can be used to keep track of things like a list of nodes to be used later on, or whether a particular import has been found.

        create(context) {
          let importsLibA = false;
          let importsLibB = false;
          const importNodes = [];
          ...
    

    To run some logic at the end of a file, the preferred approach is using the Program:exit selector. For context, Program is the topmost node of any file that contains all other nodes, and :exit is ESLint-specific syntax to specify a selector that runs when exiting a node - and not when entering. This is documented in the docs page about selectors.

    Putting it together, you should get a create function that looks more or less like this:

    create(context) {
      let importsLibA = false;
      let importsLibB = false;
      const importNodes = [];
      return {
        ImportDeclaration(node) {
          if (node.source.value.includes(`Library A`)) {
            importsLibA = true;
            importNodes.push(node);
          } else if (node.source.value.includes(`Library B`)) {
            importsLibB = true;
            importNodes.push(node);
          }
        },
        'Program:exit'() {
          if (importsLibA && importsLibB) {
            for (const node of importNodes) {
              context.report({
                node,
                messageId: 'useComponent',
              });
            }
          }
        }
      };
    }