Files
msd-core/eslint-rules/require-userprofile-with-home.cjs

115 lines
3.6 KiB
JavaScript

'use strict';
/**
* require-userprofile-with-home
*
* Flag test files that assign `process.env.HOME` without also referencing
* `USERPROFILE` anywhere in the file.
*
* ## What this enforces (G6)
*
* Program-level: collect assignments to `process.env.HOME`
* (`process.env.HOME = …` / `process.env['HOME'] = …`); track whether
* `USERPROFILE` appears anywhere in the file (any reference). At
* `Program:exit`, if HOME is assigned and `USERPROFILE` never appears, report
* each HOME assignment.
*
* Message: Windows uses `USERPROFILE`, not `HOME` — set
* `process.env.USERPROFILE` alongside.
*
* DEFECT category: DEFECT.WINDOWS-TEST-PORTABILITY
*/
/** @type {import('eslint').Rule.RuleModule} */
const rule = {
meta: {
type: 'problem',
docs: {
description:
'Require process.env.USERPROFILE to be set alongside process.env.HOME (Windows portability)',
category: 'Portability',
},
schema: [],
messages: {
missingUserProfile:
'Assigning process.env.HOME without process.env.USERPROFILE is not portable ' +
'(DEFECT.WINDOWS-TEST-PORTABILITY): Windows uses USERPROFILE as the home ' +
'directory environment variable, not HOME. Set process.env.USERPROFILE ' +
'alongside process.env.HOME.',
},
},
create(context) {
const sourceCode = context.sourceCode ?? context.getSourceCode();
/** Collected HOME assignment nodes */
const homeAssignments = [];
/** Whether a real process.env.USERPROFILE = … assignment exists in the file */
let userProfileAssigned = false;
/**
* Returns true if node is an assignment to process.env[key] or
* process.env.key for the given key name.
*
* Recognized shapes (as the left-hand side of AssignmentExpression):
* process.env.KEY — MemberExpression(MemberExpression, Identifier)
* process.env['KEY'] — MemberExpression(MemberExpression, Literal, computed=true)
*
* @param {import('eslint').Rule.Node} lhs — left side of AssignmentExpression
* @param {string} key — the env var name to check
* @returns {boolean}
*/
function isProcessEnvAssignment(lhs, key) {
if (!lhs || lhs.type !== 'MemberExpression') return false;
const obj = lhs.object;
if (!obj || obj.type !== 'MemberExpression') return false;
// obj must be process.env
if (
obj.computed ||
obj.object.type !== 'Identifier' ||
obj.object.name !== 'process' ||
obj.property.type !== 'Identifier' ||
obj.property.name !== 'env'
) {
return false;
}
// Property must be key (identifier or string literal)
if (!lhs.computed) {
return lhs.property.type === 'Identifier' && lhs.property.name === key;
} else {
return (
lhs.property.type === 'Literal' && lhs.property.value === key
);
}
}
return {
AssignmentExpression(node) {
if (isProcessEnvAssignment(node.left, 'HOME')) {
homeAssignments.push(node);
}
// Track actual USERPROFILE assignments (not mere text/comment mentions)
if (isProcessEnvAssignment(node.left, 'USERPROFILE')) {
userProfileAssigned = true;
}
},
'Program:exit'() {
if (homeAssignments.length === 0) return;
// Only suppress if USERPROFILE is actually ASSIGNED (not just mentioned in a comment)
if (userProfileAssigned) return;
for (const node of homeAssignments) {
context.report({ node, messageId: 'missingUserProfile' });
}
},
};
},
};
module.exports = rule;