UNPKG

@blaze-money/eslint-plugin-spark

Version:

ESLint plugin for Spark project with custom rules

253 lines (231 loc) 8.87 kB
module.exports = { meta: { type: "problem", docs: { description: "Enforce using ConfigService.get with EnvironmentVariables enum instead of direct process.env access", category: "Best Practices", recommended: true, }, fixable: "code", hasSuggestions: true, schema: [ { type: "object", properties: { excludedFiles: { type: "array", items: { type: "string", }, }, }, additionalProperties: false, }, ], }, create(context) { // Get excluded files from rule options const options = context.options[0] || {} const excludedFiles = options.excludedFiles || [] // Get current filename const filename = context.getFilename() // Check if current file is excluded const isExcluded = excludedFiles.some(excludedFile => filename.endsWith(excludedFile) ) // Skip rule if file is excluded if (isExcluded) { return {} } // Track if we've seen an import for EnvironmentVariables let hasEnvironmentVariablesImport = false // Keep track of whether we need to add import let needsImport = false // Keep track of violations for potential batch fixing let violations = [] // Store potential import locations let configImportNode = null let firstImportNode = null // Check if this.config or this.configService is used in the file let hasConfigServiceReference = false 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 } } } // Check for ConfigService import if (node.source.value === "@nestjs/config") { for (const specifier of node.specifiers) { if ( specifier.type === "ImportSpecifier" && specifier.imported && specifier.imported.name === "ConfigService" ) { // NestJS Config is imported break } } } }, // Check for this.config or this.configService references MemberExpression(node) { if ( node.object.type === "ThisExpression" && node.property.type === "Identifier" && (node.property.name === "config" || node.property.name === "configService") ) { hasConfigServiceReference = true } }, // Check for process.env access MemberExpression(node) { if ( node.object.type === "MemberExpression" && node.object.object.type === "Identifier" && node.object.object.name === "process" && node.object.property.type === "Identifier" && node.object.property.name === "env" && node.property.type === "Identifier" ) { const envVarName = node.property.name // Add to violations array violations.push({ node, envVarName, }) // Check if we need to add import if (!hasEnvironmentVariablesImport) { needsImport = true } context.report({ node, message: "Use ConfigService.get with EnvironmentVariables enum instead of direct process.env access", *fix(fixer) { // We need to be careful here - don't automatically fix if we don't have ConfigService if (!hasConfigServiceReference) { // If we can't confirm there's a this.config/this.configService, don't auto-fix return null } // Replace process.env.X with this.config.get(EnvironmentVariables.X) yield fixer.replaceText( node.parent.type === "MemberExpression" ? node.parent : node, `this.config.get(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 this.config.get(EnvironmentVariables.${envVarName})`, fix: fixer => { return fixer.replaceText( node.parent.type === "MemberExpression" ? node.parent : node, `this.config.get(EnvironmentVariables.${envVarName})` ) }, }, { desc: "Add EnvironmentVariables import and replace", fix: fixer => { const fixes = [ fixer.replaceText( node.parent.type === "MemberExpression" ? node.parent : node, `this.config.get(EnvironmentVariables.${envVarName})` ), ] if (!hasEnvironmentVariablesImport && firstImportNode) { fixes.push( fixer.insertTextBefore( firstImportNode, `import { EnvironmentVariables } from "@app/modules/App/config.module"\n` ) ) } return fixes }, }, ], }) } }, } }, }