Technologies:
Tolerim
25 days ago
How can I add an import declaration for a helper function if it's not already imported in a custom ESLint rule fixer?
foo.value.toString()
, with a helper function getStringValue(foo)
. While I'm able to use the fixer to replace the text on the CallExpression
node, auto-fixing this error this way creates a new issue. This is because getStringValue
may or may not already be imported into the file. To ensure that this fixer works as desired, I need to follow the guidelines below:
- If
getStringValue
is already imported into the file, do nothing. - If
getStringValue
is not imported but its containing file module is, add this function to that module's imports. - If neither
getStringValue
nor its containing file module are imported, import the module along with the function.
fixer
or context
objects. Although SourceCode.getText()
can be used to resolve the imports by parsing the source text, I'd prefer to interact directly with the entire AST. Could you guide me on the best way to perform this automatic import procedure?Answers(2)
Vogi
24 days ago
Verified Answer
According to the ESLint documentation, there is no straightforward way to access the root ESTree node using the fixer or context objects. However, you can use SourceCode.getText() to resolve the imports by parsing the source text. If you prefer to interact directly with the entire AST, you can use the NodePath object which is returned by context.getScope().getBinding('').path . This object provides access to the root node of the AST and allows you to traverse it using methods such as parent, traverse, and replaceWith. You can find more information about this object in the Babel documentation. I hope this helps!
Sources
Custom Rules - ESLint - Pluggable JavaScript LinterFind the path from root to the given nodes of a tree for multiple ...R: Root or re-root a tree at a specific node.Custom ESLint rule fixer: Adding an import declaration if a helper ...Unable to resolve dependency tree with eslint-config-airbnbTolerim
25 days ago
Instead of relying on the fixer object to perform this operation, you can use the context object to create a new import statement and add it to the top of the file. Here's an example:
const getIdentifierName = (node) => {
if (node.type === 'MemberExpression') {
return getIdentifierName(node.object) + '.' + node.property.name;
}
return node.name;
};
const getStringValueFixer = (context, node) => {
const identifierName = getIdentifierName(node);
const sourceCode = context.getSourceCode();
const importDeclarations = sourceCode.ast.body.filter(
(n) => n.type === 'ImportDeclaration',
);
let stringModuleImportName;
for (const imp of importDeclarations) {
if (imp.source.value === 'path/to/string-file') {
for (const specifier of imp.specifiers) {
if (specifier.imported.name === 'getStringValue') {
stringModuleImportName = specifier.local.name;
}
}
}
}
// If function is already imported, do nothing
if (stringModuleImportName) {
return null;
}
let stringModuleDeclaration;
// Find the import statement for the module
for (const imp of importDeclarations) {
if (imp.source.value === 'path/to/string-file') {
stringModuleDeclaration = imp;
break;
}
}
// If the module isn't imported at all, import it and the function
if (!stringModuleDeclaration) {
const importStatement = `import { getStringValue } from 'path/to/string-file';\n`;
const firstNode = sourceCode.ast.body[0];
context.report({
node,
message: `import added for getStringValue`,
fix: (fixer) => fixer.insertTextBefore(firstNode, importStatement),
});
}
// If the module is imported but function is not, add to import statement
if (stringModuleDeclaration) {
const { specifiers } = stringModuleDeclaration;
const specifierText = `{ getStringValue }`;
const lastSpecifier = specifiers[specifiers.length - 1];
const insertPos = lastSpecifier ? lastSpecifier.range[1] : stringModuleDeclaration.range[0] + 1;
context.report({
node,
message: `getStringValue added to existing import`,
fix: (fixer) => fixer.insertTextAfterRange([insertPos, insertPos], `, ${specifierText}`),
});
}
return fixer.replaceText(node, `getStringValue(${identifierName})`);
};
This implementation uses the SourceCode object to get a reference to the entire AST of the current file. It then searches through the import statements to find the module that contains the getStringValue function. If the function is already imported, the fixer simply returns null. If the module is not imported, the fixer adds a new import statement to the top of the file. If the module is already imported but the function is not, the fixer adds the function to the existing import statement. Ultimately, the fixer object is used to replace the original text with a call to getStringValue.