core/quality/eslint/no-environment-fallback.eslint.rule.ts
core/quality/eslint/no-environment-fallback.eslint.rule.ts is a file in GovLab Quality. 103 lines of code and 17 definitions.
import type { Rule } from "eslint";
interface AstNode {
type?: string;
object?: AstNode;
property?: AstNode;
name?: string;
value?: unknown;
}
const isAstNode = (value: unknown): value is AstNode => typeof value === "object" && value !== null;
const asNode = (value: unknown): AstNode | null => (isAstNode(value) ? value : null);
const safeString = (value: unknown): string => {
if (typeof value === "string") {
return value;
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
return "UNKNOWN";
};
const isProcessEnv = (value: unknown): boolean => {
const node = asNode(value);
const object = asNode(node?.object);
if (node?.type !== "MemberExpression" || object?.type !== "MemberExpression") {
return false;
}
const objectObject = asNode(object.object);
const objectProperty = asNode(object.property);
return objectObject?.type === "Identifier" && objectObject.name === "process" && objectProperty?.name === "env";
};
const isImportMetaEnv = (value: unknown): boolean => {
const node = asNode(value);
const object = asNode(node?.object);
const objectObject = asNode(object?.object);
const objectProperty = asNode(object?.property);
return (
node?.type === "MemberExpression" &&
object?.type === "MemberExpression" &&
objectObject?.type === "MetaProperty" &&
objectProperty?.name === "env"
);
};
const isEnvAccess = (value: unknown): boolean => isProcessEnv(value) || isImportMetaEnv(value);
const getEnvVarName = (value: unknown): string => {
const node = asNode(value);
const property = asNode(node?.property);
if (property?.type === "Identifier" && typeof property.name === "string") {
return property.name;
}
if (property?.type === "Literal") {
return safeString(property.value);
}
return "UNKNOWN";
};
const reportConditional = function reportConditional(context: Rule.RuleContext, node: Rule.Node): void {
if (node.type !== "ConditionalExpression" || !isEnvAccess(node.test)) {
return;
}
context.report({ data: { name: getEnvVarName(node.test) }, messageId: "noEnvTernaryFallback", node });
};
const reportLogical = function reportLogical(context: Rule.RuleContext, node: Rule.Node): void {
if (node.type !== "LogicalExpression" || (node.operator !== "||" && node.operator !== "??")) {
return;
}
if (!isEnvAccess(node.left)) {
return;
}
if (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression") {
return;
}
context.report({ data: { name: getEnvVarName(node.left) }, messageId: "noEnvFallback", node });
};
export default {
create(context): Rule.RuleListener {
const handlers: [string, (node: Rule.Node) => void][] = [
[
"ConditionalExpression",
(node): void => {
reportConditional(context, node);
},
],
[
"LogicalExpression",
(node): void => {
reportLogical(context, node);
},
],
];
return Object.fromEntries(handlers);
},
meta: {
docs: { description: "Disallow fallback values for process.env / import.meta.env access" },
messages: {
noEnvFallback:
"process.env.{{name}} must not have a fallback value. Environment variables are mandatory — " +
"if missing, fail fast. Declare {{name}} in the project's secret store and read it through the store's typed accessor, which stops the run when the value is missing. [no_environment_fallback]",
noEnvTernaryFallback:
"process.env.{{name}} must not have a ternary fallback. Environment variables are mandatory — " +
"validate presence at startup, never provide defaults. [no_environment_fallback]",
},
schema: [],
type: "problem",
},
} satisfies Rule.RuleModule;