Add import declaration if helper function not already imported: Enhanced solution with custom ESLint rules fix
P粉312195700
P粉312195700 2024-03-28 09:26:00
0
2
330

I have a use case where I want to replace variable function calls, specifically foo.value.toString(), with a helper function getStringValue(foo). If I find it, I can use the fix to replace the text on the CallExpression node, so my rule fix currently looks like this:

fixer => fixer.replaceText(node, `getStringValue(${identifierNode.getText()})`);

The problem with automatically fixing this error this way is that getStringValue may or may not have been imported into the file. I want this fix to have the following behavior:

  1. If the function has been imported into the file, no additional action is required.
  2. If the function is not imported, but the file module it contains is imported, please add this function to the import of the module.
  3. If neither the function nor the file module it contains is imported, import the module together with the function.

As far as I understand from the documentation, there is no easy way to access the root ESTree node using a fixer or context object. The closest is SourceCode.getText(), which means I have to parse the source text to resolve the import - I'd rather interact with the entire AST directly. What is the best way to perform this automated import process?

P粉312195700
P粉312195700

reply all(2)
P粉596161915

If you want to be slightly unsafe here, you can assume that the user has not redefined the getStringValue function locally in their files (usually a safe assumption if you have a codebase to which this rule applies ).

In this case, the best way is to use a selector to check the import, for example:

module.exports = {
  create(context) {
    let hasImport = false;
    let lastImport = null;
    return {
      ImportDeclaration(node) {
        lastImport = node;
        if (isImportICareAbout(node)) {
          hasImport = true;
        }
      },
      "My Selector For Other Linting Logic"(node) {
        // ...
        context.report({
          messageId: "myReport",
          fix(fixer) {
            const fixes = [
              fixer.replaceText(node, `getStringValue(${identifierNode.name})`),
            ];
            if (!hasImport) {
              const newImport = 'import { getStringValue } from "module";';
              if (lastImport) {
                // insert after the last import decl
                fixes.push(fixer.insertTextBefore(lastImport, newImport));
              } else {
                // insert at the start of the file
                fixes.push(fixer.insertTextAfterRange([0, 0], newImport));
              }
            }
            return fixes;
          },
        });
      },
    };
  },
};
P粉098979048

It turns out there is an easy way to extract the AST root node from the context object. It is located at context.getSourceCode().ast. I rewrote my fix with the following logic:

fixer => {
  fixer.replaceText(node, `getStringValue(${identifierNode.getText()})`);
  const body = context.getSourceCode().ast;
  const importDeclarations = body.filter(statement => statement.type === AST_NODE_TYPES.ImportDeclaration);
  ... // Check if the declaration has the import and add the appropriate imports if necessary
}
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template