@blaze-money/eslint-plugin-spark
Version:
ESLint plugin for Spark project with custom rules
195 lines (180 loc) • 7.45 kB
JavaScript
module.exports = {
meta: {
type: "problem",
docs: {
description:
"Enforce using EnvironmentVariables enum instead of raw strings with ConfigService",
category: "Best Practices",
recommended: true,
},
fixable: "code",
hasSuggestions: true,
schema: [], // no options
},
create(context) {
// Track if we've seen an import for EnvironmentVariables
let hasEnvironmentVariablesImport = false
// Track if we need to add an import and have found violations
let needsImport = false
// Keep track of violations for potential batch fixing
let violations = []
// Store potential import locations
let configImportNode = null
let firstImportNode = null
return {
// Check for existing imports of EnvironmentVariables
ImportDeclaration(node) {
// Store the first import we encounter for later use
if (!firstImportNode) {
firstImportNode = node
}
if (
node.source.value === "@config.module" ||
node.source.value === "@app/modules/App/config.module" ||
node.source.value.includes("config.module")
) {
// Store this as a potential location to add EnvironmentVariables import
configImportNode = node
// Check if EnvironmentVariables is one of the imported specifiers
for (const specifier of node.specifiers) {
if (
specifier.type === "ImportSpecifier" &&
specifier.imported &&
specifier.imported.name === "EnvironmentVariables"
) {
hasEnvironmentVariablesImport = true
break
}
}
}
},
CallExpression(node) {
// Check if this is a get or get method call on config or configService
if (
node.callee.type === "MemberExpression" &&
node.callee.property.type === "Identifier" &&
(node.callee.property.name === "get" ||
node.callee.property.name === "get") &&
node.callee.object.type === "MemberExpression" &&
node.callee.object.object.type === "ThisExpression" &&
node.callee.object.property.type === "Identifier" &&
(node.callee.object.property.name === "config" ||
node.callee.object.property.name === "configService")
) {
// Check if we have arguments and first argument is a string literal
if (
node.arguments.length > 0 &&
node.arguments[0].type === "Literal" &&
typeof node.arguments[0].value === "string"
) {
const envVarName = node.arguments[0].value
// Add to violations array
violations.push({
node,
envVarName,
})
// We need to add an import if we don't have one already
if (!hasEnvironmentVariablesImport) {
needsImport = true
}
context.report({
node,
message:
"Use EnvironmentVariables enum instead of string literals for ConfigService.get calls",
*fix(fixer) {
// First replace the string literal with EnvironmentVariables.X
yield fixer.replaceText(
node.arguments[0],
`EnvironmentVariables.${envVarName}`
)
// If we need to add an import and this is the first violation being fixed
if (needsImport && node === violations[0].node) {
// If we found an existing config module import, add EnvironmentVariables to it
if (configImportNode) {
// If this is a named import with {}, add to it
const hasNamedImports = configImportNode.specifiers.some(
s => s.type === "ImportSpecifier"
)
if (hasNamedImports) {
// Find the closing brace of the import specifiers
const sourceCode = context.getSourceCode()
const text = sourceCode.getText(configImportNode)
const openBraceIndex = text.indexOf("{")
const closeBraceIndex = text.indexOf("}")
if (openBraceIndex >= 0 && closeBraceIndex >= 0) {
// If there are existing specifiers, add a comma
const hasSpecifiers =
configImportNode.specifiers.filter(
s => s.type === "ImportSpecifier"
).length > 0
const insertText = hasSpecifiers
? ", EnvironmentVariables"
: "EnvironmentVariables"
yield fixer.insertTextBeforeRange(
[
configImportNode.range[0] + closeBraceIndex,
configImportNode.range[0] + closeBraceIndex,
],
insertText
)
}
} else {
// If it's a default import only, add a named import part
yield fixer.insertTextAfter(
configImportNode,
", { EnvironmentVariables }"
)
}
} else if (firstImportNode) {
// Add a new import statement before the first import
yield fixer.insertTextBefore(
firstImportNode,
`import { EnvironmentVariables } from "@app/modules/App/config.module"\n`
)
} else {
// No imports found, add at the beginning of the file
yield fixer.insertTextBefore(
context.getSourceCode().ast,
`import { EnvironmentVariables } from "@app/modules/App/config.module"\n\n`
)
}
}
},
suggest: [
{
desc: `Replace with EnvironmentVariables.${envVarName}`,
fix: fixer => {
return fixer.replaceText(
node.arguments[0],
`EnvironmentVariables.${envVarName}`
)
},
},
{
desc: "Add EnvironmentVariables import and replace",
fix: fixer => {
const fixes = [
fixer.replaceText(
node.arguments[0],
`EnvironmentVariables.${envVarName}`
),
]
if (!hasEnvironmentVariablesImport && firstImportNode) {
fixes.push(
fixer.insertTextBefore(
firstImportNode,
`import { EnvironmentVariables } from "@app/modules/App/config.module"\n`
)
)
}
return fixes
},
},
],
})
}
}
},
}
},
}