@jenssimon/eslint-config-sfcc
Version:
A collection of shareable ESLint configurations for Salesforce Commerce Cloud (SFCC)
877 lines (876 loc) • 31 kB
JavaScript
import { fixupPluginRules } from "@eslint/compat";
import es from "eslint-plugin-es";
import globals from "globals";
import fs from "node:fs";
import path from "node:path";
import { XMLParser } from "fast-xml-parser";
//#region src/plugins/_utils/sfcc-settings.ts
function getSfccSettings(context) {
return context.settings?.sfcc ?? {};
}
function withSfccSettings(createWithSettings) {
return (context) => createWithSettings(context, getSfccSettings(context));
}
//#endregion
//#region src/plugins/sfcc/no-ds-files.ts
function isDsFile(filename) {
return /\.ds$/iu.test(filename);
}
const noDsFiles = {
meta: {
type: "problem",
docs: {
description: "Disallow .ds files in SFCC projects. Use .js files instead.",
recommended: true
},
schema: [],
messages: { noDsFiles: "Legacy .ds files are not allowed. Rename this file to .js and use CommonJS modules." }
},
create: withSfccSettings((context, _sfccSettings) => {
return { Program(node) {
if (!isDsFile(context.filename)) return;
context.report({
node,
messageId: "noDsFiles"
});
} };
})
};
//#endregion
//#region src/plugins/sfcc/no-e4x-syntax.ts
function isAstNode(value) {
return value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
}
function hasDynamicJsxParts(root) {
const stack = [root];
const seen = /* @__PURE__ */ new Set();
while (stack.length > 0) {
const node = stack.pop();
if (!node || seen.has(node)) continue;
seen.add(node);
if (node.type === "JSXExpressionContainer" || node.type === "JSXSpreadAttribute" || node.type === "JSXSpreadChild") return true;
const entries = Object.entries(node);
for (const [key, value] of entries) {
if (key === "parent" || value === null || value === void 0) continue;
if (Array.isArray(value)) {
for (const item of value) if (isAstNode(item)) stack.push(item);
continue;
}
if (isAstNode(value)) stack.push(value);
}
}
return false;
}
function toXmlSuggestionText(markup) {
if (markup.includes("\n") && !markup.includes("`") && !markup.includes("${")) return `XML(\`${markup}\`)`;
return `XML(${JSON.stringify(markup)})`;
}
const noE4xSyntax = {
meta: {
type: "problem",
docs: {
description: "Disallow JSX/E4X-like syntax in SFCC JavaScript to avoid parser ambiguity and unsupported runtime patterns.",
recommended: true
},
hasSuggestions: true,
schema: [],
messages: {
forbiddenSyntax: "JSX/E4X-like syntax is not allowed in SFCC JavaScript. Use plain JavaScript and SFCC XML APIs instead.",
suggestXmlCtor: "Convert static markup to XML(\"...\") for explicit XML construction."
}
},
create: withSfccSettings((context) => {
return {
JSXElement(node) {
const isDynamic = hasDynamicJsxParts(node);
context.report({
node,
messageId: "forbiddenSyntax",
...isDynamic ? {} : { suggest: [{
messageId: "suggestXmlCtor",
fix: (fixer) => fixer.replaceText(node, toXmlSuggestionText(context.sourceCode.getText(node)))
}] }
});
},
JSXFragment(node) {
context.report({
node,
messageId: "forbiddenSyntax"
});
}
};
})
};
//#endregion
//#region src/plugins/sfcc/no-rhino-import-globals.ts
const LEGACY_RHINO_IMPORTS = new Set([
"importScript",
"importPackage",
"importClass"
]);
function isJavaScriptTarget$1(filename) {
if (filename === "<input>") return true;
return /\.(?:[cm]?js|ds)$/iu.test(filename);
}
const noRhinoImportGlobals = {
meta: {
type: "problem",
docs: {
description: "Disallow legacy Rhino globals importScript, importPackage, and importClass in JavaScript files. Use CommonJS require() instead.",
recommended: true
},
schema: [],
messages: { forbiddenLegacyImport: "{{name}}() is a legacy Rhino global. Use CommonJS require() instead." }
},
create: withSfccSettings((context) => {
if (!isJavaScriptTarget$1(context.filename)) return {};
return { CallExpression(node) {
const callee = node.callee;
if (callee.type !== "Identifier" || !LEGACY_RHINO_IMPORTS.has(callee.name ?? "")) return;
context.report({
node: callee,
messageId: "forbiddenLegacyImport",
data: { name: callee.name }
});
} };
})
};
//#endregion
//#region src/plugins/sfcc/no-type-annotations.ts
function isJavaScriptTarget(filename) {
if (filename === "<input>") return true;
return /\.(?:[cm]?js|ds)$/iu.test(filename);
}
function canAutoFix(node) {
const parent = node.parent;
if (!parent) return false;
if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") return true;
if (parent.type !== "Identifier") return false;
if (parent.optional === true || parent.definite === true) return false;
if (parent.name === "this") return false;
return true;
}
function hasLeadingJsdoc(sourceCode, node) {
return sourceCode.getCommentsBefore(node).some((comment) => comment.type === "Block" && comment.value.trimStart().startsWith("*"));
}
function getTypeText(sourceCode, node) {
return sourceCode.getText(node).replace(/^:\s*/u, "");
}
function getVariableDeclaration(node) {
const identifier = node.parent;
const declarator = identifier?.parent;
const declaration = declarator?.parent;
if (!identifier || identifier.type !== "Identifier") return;
if (declarator?.type !== "VariableDeclarator" || declaration?.type !== "VariableDeclaration") return;
if (declaration.declarations.length !== 1 || declarator.id !== identifier || declarator.init == null) return;
return declaration;
}
function getFunctionNode(node) {
const parent = node.parent;
if (parent?.type === "FunctionDeclaration" || parent?.type === "FunctionExpression" || parent?.type === "ArrowFunctionExpression") return parent;
}
const noTypeAnnotations = {
meta: {
type: "problem",
docs: {
description: "Disallow type-annotation syntax in JavaScript files. Rhino/E4X may accept it, but it is invalid in standard JavaScript. Use JSDoc types instead.",
recommended: true
},
fixable: "code",
hasSuggestions: true,
schema: [],
messages: {
forbiddenTypeAnnotation: "Type annotation syntax may be valid in Rhino/E4X, but is invalid in standard JavaScript and not allowed in .js files. Use JSDoc typing instead.",
suggestJsdocType: "Add a JSDoc @type annotation and remove the inline type annotation.",
suggestJsdocReturns: "Add a JSDoc @returns annotation and remove the inline type annotation."
}
},
create: withSfccSettings((context) => {
if (!isJavaScriptTarget(context.filename)) return {};
return { TSTypeAnnotation(rawNode) {
const node = rawNode;
const sourceCode = context.sourceCode;
const variableDeclaration = getVariableDeclaration(node);
const functionNode = getFunctionNode(node);
const typeText = getTypeText(sourceCode, node);
const shouldSuggestTypeJsdoc = variableDeclaration !== void 0 && !hasLeadingJsdoc(sourceCode, variableDeclaration);
const shouldSuggestReturnsJsdoc = functionNode !== void 0 && !hasLeadingJsdoc(sourceCode, functionNode);
context.report({
node,
messageId: "forbiddenTypeAnnotation",
...canAutoFix(node) ? { fix: (fixer) => fixer.remove(node) } : {},
...shouldSuggestTypeJsdoc && variableDeclaration !== void 0 ? { suggest: [{
messageId: "suggestJsdocType",
fix: (fixer) => [fixer.insertTextBefore(variableDeclaration, `/** @type {${typeText}} */\n`), fixer.remove(node)]
}] } : shouldSuggestReturnsJsdoc && functionNode !== void 0 ? { suggest: [{
messageId: "suggestJsdocReturns",
fix: (fixer) => [fixer.insertTextBefore(functionNode, `/** @returns {${typeText}} */\n`), fixer.remove(node)]
}] } : {}
});
} };
})
};
//#endregion
//#region src/plugins/_utils/rhino-scope.ts
const FUNCTION_NODE_TYPES = new Set([
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression"
]);
const LOOP_NODE_TYPES = new Set([
"ForStatement",
"ForInStatement",
"ForOfStatement",
"WhileStatement",
"DoWhileStatement"
]);
function isFunctionBodyBlock(node) {
const parent = node.parent;
if (!parent) return false;
return FUNCTION_NODE_TYPES.has(parent.type) && parent.body === node;
}
function hasLoopAncestor(node) {
let current = node.parent;
while (current) {
if (LOOP_NODE_TYPES.has(current.type)) return true;
if (FUNCTION_NODE_TYPES.has(current.type) || current.type === "Program") return false;
current = current.parent;
}
return false;
}
/**
* Returns true when a VariableDeclaration node sits in a block-scoped context
* where Rhino's const implementation is broken due to loop re-execution —
* i.e. the same const binding would be re-declared on each iteration.
*
* Affected contexts (see https://github.com/mozilla/rhino/issues/326):
* - The left-hand side of for-in / for-of statements
* - The init position of a standard for-loop
* - Any BlockStatement that has a loop ancestor within the same function
* - SwitchCase (iterated via fall-through semantics in Rhino)
*/
function isRhinoCriticalScope(node) {
const parent = node.parent;
if (!parent) return false;
if (parent.type === "Program") return false;
if (parent.type === "BlockStatement") {
if (isFunctionBodyBlock(parent)) return false;
return hasLoopAncestor(node);
}
return parent.type === "ForStatement" || parent.type === "ForInStatement" || parent.type === "ForOfStatement" || parent.type === "SwitchCase";
}
/**
* Returns true when a VariableDeclaration node is inside a nested block
* (any BlockStatement that is NOT the direct body of a function).
* Used to detect potential same-name const conflicts across sibling blocks.
*/
function isInNestedBlock(node) {
const parent = node.parent;
if (!parent || parent.type !== "BlockStatement") return false;
return !isFunctionBodyBlock(parent);
}
//#endregion
//#region src/plugins/sfcc/prefer-const.ts
function isNeverReassigned(variable) {
return variable.references.every((ref) => !ref.isWrite() || ref.init === true);
}
const preferConst = {
meta: {
type: "suggestion",
docs: {
description: "Require const for let declarations that are never reassigned, except in Rhino-sensitive nested and loop scopes.",
recommended: true
},
fixable: "code",
schema: [],
messages: { useConst: "Prefer const over {{kind}} when the variable is never reassigned." }
},
create: withSfccSettings((context) => {
function checkDeclaration(node) {
if (isInNestedBlock(node) || isRhinoCriticalScope(node)) return;
const variables = context.sourceCode.getDeclaredVariables(node);
if (variables.length === 0 || !variables.every(isNeverReassigned)) return;
const kind = node.kind;
context.report({
node,
messageId: "useConst",
data: { kind },
fix: (fixer) => {
const keywordToken = context.sourceCode.getFirstToken(node);
if (!keywordToken || keywordToken.value !== kind) return null;
return fixer.replaceText(keywordToken, "const");
}
});
}
function checkVarDeclaration(node) {
if (isRhinoCriticalScope(node)) return;
const variables = context.sourceCode.getDeclaredVariables(node);
if (variables.length === 0 || !variables.every(isNeverReassigned)) return;
context.report({
node,
messageId: "useConst",
data: { kind: "var" },
fix: (fixer) => {
const keywordToken = context.sourceCode.getFirstToken(node);
if (!keywordToken || keywordToken.value !== "var") return null;
return fixer.replaceText(keywordToken, "const");
}
});
}
return {
"VariableDeclaration[kind='let']": checkDeclaration,
"VariableDeclaration[kind='var']": checkVarDeclaration
};
})
};
//#endregion
//#region src/plugins/sfcc/rhino-const-compat.ts
function isRhinoCriticalConstDeclaration(node) {
return node.type === "VariableDeclaration" && node.kind === "const" && isRhinoCriticalScope(node);
}
const rhinoConstCompat = {
meta: {
type: "problem",
docs: {
description: "Enforce let instead of const in Rhino-unsafe loop-related scopes.",
recommended: true
},
fixable: "code",
schema: [],
messages: { useLet: "Use let instead of const in this block-scoped context for Rhino compatibility." }
},
create: withSfccSettings((context) => ({ VariableDeclaration(node) {
if (!isRhinoCriticalConstDeclaration(node)) return;
context.report({
node,
messageId: "useLet",
fix: (fixer) => {
const keywordToken = context.sourceCode.getFirstToken(node);
if (!keywordToken || keywordToken.value !== "const") return null;
return fixer.replaceText(keywordToken, "let");
}
});
} }))
};
//#endregion
//#region src/plugins/sfcc/rhino-const-conflict.ts
const rhinoConstConflict = {
meta: {
type: "problem",
docs: {
description: "Disallow const in nested blocks when the same identifier is declared as const elsewhere in the same function, as Rhino treats const as function-scoped and would throw a re-declaration error.",
recommended: true
},
fixable: "code",
schema: [],
messages: { conflict: "'{{name}}' is declared as const in multiple block scopes of the same function. Use let to avoid a Rhino re-declaration error." }
},
create: withSfccSettings((context) => {
const stack = [];
function enterFunction() {
stack.push({
allConsts: /* @__PURE__ */ new Map(),
nestedConsts: /* @__PURE__ */ new Map()
});
}
function exitFunction() {
const scope = stack.pop();
if (!scope) return;
for (const [name, nestedNodes] of scope.nestedConsts) if ((scope.allConsts.get(name)?.length ?? 0) > 1) for (const node of nestedNodes) context.report({
node,
messageId: "conflict",
data: { name },
fix: (fixer) => {
const token = context.sourceCode.getFirstToken(node);
if (!token || token.value !== "const") return null;
return fixer.replaceText(token, "let");
}
});
}
return {
FunctionDeclaration: enterFunction,
FunctionExpression: enterFunction,
ArrowFunctionExpression: enterFunction,
"FunctionDeclaration:exit": exitFunction,
"FunctionExpression:exit": exitFunction,
"ArrowFunctionExpression:exit": exitFunction,
VariableDeclaration(rawNode) {
if (rawNode.kind !== "const" || stack.length === 0) return;
const node = rawNode;
const scope = stack[stack.length - 1];
const nested = isInNestedBlock(node);
const variables = context.sourceCode.getDeclaredVariables(rawNode);
for (const variable of variables) {
const { name } = variable;
if (!scope.allConsts.has(name)) scope.allConsts.set(name, []);
scope.allConsts.get(name).push(node);
if (nested) {
if (!scope.nestedConsts.has(name)) scope.nestedConsts.set(name, []);
scope.nestedConsts.get(name).push(node);
}
}
}
};
})
};
//#endregion
//#region src/plugins/_utils/site-template-cartridge-path.ts
function resolveSiteTemplateXmlPath(siteTemplatePath, site, cwd) {
if (!siteTemplatePath || !site) return;
const resolvedSiteTemplatePath = path.isAbsolute(siteTemplatePath) ? siteTemplatePath : path.resolve(cwd, siteTemplatePath);
return path.join(resolvedSiteTemplatePath, "sites", site, "site.xml");
}
function findCustomCartridges(value) {
if (!value || typeof value !== "object") return;
if (Array.isArray(value)) {
for (const item of value) {
const found = findCustomCartridges(item);
if (found) return found;
}
return;
}
const objectValue = value;
const directValue = objectValue["custom-cartridges"];
if (typeof directValue === "string") return directValue;
for (const nestedValue of Object.values(objectValue)) {
const found = findCustomCartridges(nestedValue);
if (found) return found;
}
}
function getSiteTemplateCartridgePath(siteTemplatePath, site, cwd) {
const siteTemplateXmlPath = resolveSiteTemplateXmlPath(siteTemplatePath, site, cwd);
if (!siteTemplateXmlPath) return [];
try {
const xmlContent = fs.readFileSync(siteTemplateXmlPath, "utf8");
const customCartridges = findCustomCartridges(new XMLParser({
ignoreAttributes: true,
trimValues: true,
parseTagValue: false,
parseAttributeValue: false
}).parse(xmlContent));
if (!customCartridges) return [];
return customCartridges.split(":").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
} catch {
return [];
}
}
//#endregion
//#region src/plugins/sfcc/valid-require-path.ts
const SUPPORTED_EXTENSIONS = [
"js",
"ds",
"json"
];
function getStringArgument(node) {
if (node.type === "Literal" && typeof node.value === "string") return node.value;
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0]?.value.cooked ?? void 0;
}
function isAllowedPrefix(requirePath) {
return requirePath.startsWith("dw/") || requirePath.startsWith("./") || requirePath.startsWith("../") || requirePath.startsWith("*/") || requirePath.startsWith("~/");
}
function isCartridgeStylePath(requirePath) {
return /^[A-Za-z0-9_-]+\/.+/u.test(requirePath);
}
function getFirstSegment(requirePath) {
const slashIndex = requirePath.indexOf("/");
return slashIndex === -1 ? requirePath : requirePath.slice(0, slashIndex);
}
function resolveCartridgesDir(cartridgesDir, cwd) {
return path.isAbsolute(cartridgesDir) ? cartridgesDir : path.resolve(cwd, cartridgesDir);
}
function getConfiguredCartridgePath(cartridgePath) {
if (!cartridgePath) return [];
return cartridgePath.filter((entry) => entry.trim().length > 0);
}
function getFilesystemCartridges(cartridgesDir, cwd) {
const baseDir = resolveCartridgesDir(cartridgesDir, cwd);
try {
return fs.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
} catch {
return [];
}
}
function moduleExistsInCartridge(cartridgeName, moduleTarget, cartridgesDir, cwd) {
const baseDir = resolveCartridgesDir(cartridgesDir, cwd);
const normalizedTarget = moduleTarget.replace(/^\/+/, "");
const targetPath = path.join(baseDir, cartridgeName, normalizedTarget);
if (path.extname(normalizedTarget)) return fs.existsSync(targetPath);
return SUPPORTED_EXTENSIONS.some((extension) => fs.existsSync(`${targetPath}.${extension}`));
}
function getOwnCartridge(filename, cartridgesDir, cwd) {
if (filename === "<input>") return;
const resolvedFilename = path.isAbsolute(filename) ? filename : path.resolve(cwd, filename);
const baseDir = resolveCartridgesDir(cartridgesDir, cwd);
const relativePath = path.relative(baseDir, resolvedFilename);
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) return;
const firstSegment = relativePath.split(path.sep)[0];
return firstSegment && firstSegment !== "." ? firstSegment : void 0;
}
function starReferenceExists(requirePath, cartridgesDir, cwd, cartridgePath) {
const moduleTarget = requirePath.slice(2);
if (!moduleTarget) return false;
return (cartridgePath.length > 0 ? cartridgePath : getFilesystemCartridges(cartridgesDir, cwd)).some((name) => moduleExistsInCartridge(name, moduleTarget, cartridgesDir, cwd));
}
function tildeReferenceExists(requirePath, filename, cartridgesDir, cwd) {
const moduleTarget = requirePath.slice(2);
if (!moduleTarget) return false;
const ownCartridge = getOwnCartridge(filename, cartridgesDir, cwd);
if (!ownCartridge) return false;
return moduleExistsInCartridge(ownCartridge, moduleTarget, cartridgesDir, cwd);
}
function cartridgeExists(cartridgeName, cartridgesDir, cwd) {
const baseDir = resolveCartridgesDir(cartridgesDir, cwd);
const cartridgeRoot = path.join(baseDir, cartridgeName);
try {
return fs.statSync(cartridgeRoot).isDirectory();
} catch {
return false;
}
}
//#endregion
//#region src/plugins/sfcc/index.ts
const sfcc = { rules: {
"no-ds-files": noDsFiles,
"no-e4x-syntax": noE4xSyntax,
"no-type-annotations": noTypeAnnotations,
"no-rhino-import-globals": noRhinoImportGlobals,
"prefer-const": preferConst,
"rhino-const-compat": rhinoConstCompat,
"rhino-const-conflict": rhinoConstConflict,
"valid-require-path": {
meta: {
type: "problem",
docs: {
description: "Enforce SFCC-compatible require paths (dw/, relative, cartridge-name/, */, ~/).",
recommended: true
},
schema: [],
messages: {
invalidPath: "Invalid require path \"{{requirePath}}\". Allowed: dw/*, cartridgeName/*, ./*, ../*, */*, ~/* or configured bare modules.",
unknownCartridge: "Unknown cartridge \"{{cartridgeName}}\" in require path \"{{requirePath}}\" (checked in \"{{cartridgesDir}}/\").",
unresolvedStarPath: "Cannot resolve \"{{requirePath}}\" against configured cartridges in \"{{cartridgesDir}}/\".",
unresolvedTildePath: "Cannot resolve \"{{requirePath}}\" in current cartridge (checked in \"{{cartridgesDir}}/\")."
}
},
create: withSfccSettings((context, options) => {
const allowBareModules = new Set(options.allowBareModules ?? ["server"]);
const checkCartridgeExists = options.checkCartridgeExists === true;
const cartridgesDir = options.cartridgesDir ?? "cartridges";
const cwd = context.cwd ?? context.getCwd?.() ?? process.cwd();
const configuredCartridgePath = getConfiguredCartridgePath(options.cartridgePath);
const templateCartridgePath = getSiteTemplateCartridgePath(options.siteTemplatePath, options.site, cwd);
const cartridgePath = configuredCartridgePath.length > 0 ? configuredCartridgePath : templateCartridgePath;
const filename = context.filename ?? context.getFilename?.() ?? "<input>";
return { CallExpression(node) {
const callNode = node;
if (callNode.callee?.type !== "Identifier" || callNode.callee.name !== "require") return;
const firstArgument = callNode.arguments?.[0];
if (!firstArgument) return;
const requirePath = getStringArgument(firstArgument);
if (!requirePath) return;
if (requirePath.startsWith("*/")) {
if (checkCartridgeExists && !starReferenceExists(requirePath, cartridgesDir, cwd, cartridgePath)) context.report({
node: firstArgument,
messageId: "unresolvedStarPath",
data: {
requirePath,
cartridgesDir
}
});
return;
}
if (requirePath.startsWith("~/")) {
if (checkCartridgeExists && !tildeReferenceExists(requirePath, filename, cartridgesDir, cwd)) context.report({
node: firstArgument,
messageId: "unresolvedTildePath",
data: {
requirePath,
cartridgesDir
}
});
return;
}
if (isAllowedPrefix(requirePath)) return;
if (!requirePath.includes("/")) {
if (!allowBareModules.has(requirePath)) context.report({
node: firstArgument,
messageId: "invalidPath",
data: { requirePath }
});
return;
}
if (!isCartridgeStylePath(requirePath)) {
context.report({
node: firstArgument,
messageId: "invalidPath",
data: { requirePath }
});
return;
}
if (!checkCartridgeExists) return;
const cartridgeName = getFirstSegment(requirePath);
if (!cartridgeExists(cartridgeName, cartridgesDir, cwd)) context.report({
node: firstArgument,
messageId: "unknownCartridge",
data: {
cartridgeName,
requirePath,
cartridgesDir
}
});
} };
})
}
} };
//#endregion
//#region src/plugins/sitegenesis/no-global-require.ts
function getParserGlobalReturn(context) {
const parserOptions = context.parserOptions;
return Boolean(parserOptions?.ecmaFeatures?.globalReturn);
}
function getGlobalScope(context, node) {
const scope = context.sourceCode.getScope(node);
const hasGlobalReturn = getParserGlobalReturn(context);
const sourceType = node.sourceType;
if (hasGlobalReturn || sourceType === "module" || sourceType === "commonjs") return scope.childScopes[0] ?? scope;
return scope;
}
function isReadOnlyVariable(variable) {
return variable.writeable === void 0;
}
function getRequireCalleeName(variable) {
return (variable.defs[0]?.node).init?.callee?.name;
}
function getReportNode(variable) {
const identifierNode = variable.identifiers[0];
if (identifierNode) return identifierNode;
return variable.defs[0]?.node;
}
//#endregion
//#region src/plugins/sitegenesis/index.ts
const sitegenesis = { rules: { "no-global-require": {
meta: {
docs: {
description: "Prohibites global use of require unless every function is using it.",
recommended: true
},
schema: []
},
create: (context) => {
if (!context.filename.replaceAll("\\", "/").includes("/cartridge/controllers/")) return {};
let routeCount = 0;
const requires = {};
function processFunction(node) {
const scope = context.sourceCode.getScope(node);
if (scope.upper?.block?.type !== "Program") return;
routeCount += 1;
scope.through.filter((item) => item.from.type === "function").forEach((item) => {
const requireItem = requires[item.identifier.name];
if (requireItem) requireItem.useCount += 1;
});
}
return {
Program(node) {
const globalScope = getGlobalScope(context, node);
globalScope.variables.filter(isReadOnlyVariable).filter((item) => item.name !== "arguments").forEach((item) => {
const isRequire = getRequireCalleeName(item) === "require";
const usedGlobally = globalScope.references.some((ref) => !ref.init && ref.identifier.name === item.name);
if (isRequire) requires[item.name] = {
variable: item,
useCount: 0,
usedGlobally
};
});
},
FunctionExpression(node) {
processFunction(node);
},
FunctionDeclaration(node) {
processFunction(node);
},
"Program:exit"() {
Object.keys(requires).forEach((key) => {
const value = requires[key];
const reportNode = value ? getReportNode(value.variable) : void 0;
if (value && reportNode && value.useCount < routeCount && !value.usedGlobally) context.report({
node: reportNode,
message: "\"{{a}}\" should be declared inside route",
data: { a: key }
});
});
}
};
}
} } };
//#endregion
//#region src/rules/core.ts
const core = {
"no-restricted-properties": "off",
"object-shorthand": "off",
"prefer-arrow-callback": "off",
"prefer-const": "off",
"prefer-exponentiation-operator": "off",
"prefer-object-has-own": "off",
"prefer-object-spread": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"preserve-caught-error": "off"
};
//#endregion
//#region src/rules/es.ts
const esVersionPresets = ["no-new-in-esnext"];
for (let year = 2020; year >= 2016; year--) esVersionPresets.push(`no-new-in-es${year}`);
const getRulesFromPreset = (preset) => Object.entries(es.configs[preset]?.rules ?? {});
const getRulesFromPresets = (presets) => Object.fromEntries(presets.flatMap(getRulesFromPreset));
const es$1 = {
...getRulesFromPresets(esVersionPresets),
"es/no-object-values": "off",
"es/no-object-entries": "off",
"es/no-for-of-loops": "off",
"es/no-classes": "error",
"es/no-computed-properties": "error",
"es/no-default-parameters": "error",
"es/no-dynamic-import": "error",
"es/no-generators": "error",
"es/no-modules": "error",
"es/no-new-target": "error",
"es/no-promise": "error",
"es/no-proxy": "error",
"es/no-reflect": "error",
"es/no-regexp-u-flag": "error",
"es/no-regexp-y-flag": "error",
"es/no-rest-parameters": "error",
"es/no-rest-spread-properties": "error",
"es/no-spread-elements": "error",
"es/no-subclassing-builtins": "error"
};
//#endregion
//#region src/rules/sfcc.ts
const sfcc$1 = {
"sfcc/no-ds-files": "error",
"sfcc/no-e4x-syntax": "error",
"sfcc/no-type-annotations": "error",
"sfcc/no-rhino-import-globals": "error",
"sfcc/prefer-const": "error",
"sfcc/rhino-const-compat": "error",
"sfcc/rhino-const-conflict": "error",
"sfcc/valid-require-path": "error"
};
//#endregion
//#region src/rules/sitegenesis.ts
const sitegenesis$1 = { "sitegenesis/no-global-require": "error" };
//#endregion
//#region src/rules/sonarjs.ts
const sonarjs = { "sonarjs/no-implicit-global": "off" };
//#endregion
//#region src/rules/typescript-eslint.ts
const typescriptEslint = { "@typescript-eslint/no-require-imports": "off" };
//#endregion
//#region src/rules/unicorn.ts
const unicorn = {
"unicorn/no-array-for-each": "off",
"unicorn/no-array-sort": "off",
"unicorn/no-for-loop": "off",
"unicorn/no-useless-iterator-to-array": "off",
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-array-flat": "off",
"unicorn/prefer-array-flat-map": "off",
"unicorn/prefer-at": "off",
"unicorn/prefer-default-parameters": "off",
"unicorn/prefer-modern-math-apis": "off",
"unicorn/prefer-module": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/prefer-optional-catch-binding": "off",
"unicorn/prefer-reflect-apply": "off",
"unicorn/prefer-structured-clone": "off",
"unicorn/prefer-string-replace-all": "off",
"unicorn/prefer-spread": "off"
};
//#endregion
//#region src/rules/index.ts
const rules = {
...core,
...unicorn,
...sonarjs,
...typescriptEslint,
...es$1,
...sfcc$1,
...sitegenesis$1
};
//#endregion
//#region src/sfcc-globals.ts
const sfccGlobals = {
dw: true,
global: true,
APIException: true,
ConversionError: true,
customer: true,
empty: true,
Fault: true,
IOError: true,
Iterator: true,
PIPELET_ERROR: true,
PIPELET_NEXT: true,
QName: true,
request: true,
response: true,
session: true,
slotcontent: true,
StopIteration: true,
SystemError: true,
webreferences: true,
webreferences2: true,
XML: true,
XMLList: true,
XMLStreamError: true,
importScript: true,
importPackage: true,
importClass: true
};
//#endregion
//#region src/configs/recommended.ts
/** Creates the recommended flat config for SFCC projects. */
function createRecommendedConfig(options = {}) {
const { cartridgesDir = "cartridges", files, ignores, sfcc: sfccOptions } = options;
const normalizedCartridgesDir = cartridgesDir.replace(/\/+$/u, "") || "/";
const sfccSettings = sfccOptions !== void 0 && (sfccOptions.allowBareModules !== void 0 || sfccOptions.checkCartridgeExists !== void 0 || sfccOptions.cartridgePath !== void 0 || sfccOptions.cartridgesDir !== void 0 || sfccOptions.siteTemplatePath !== void 0 || sfccOptions.site !== void 0) ? {
...sfccOptions,
...sfccOptions?.cartridgesDir === void 0 ? { cartridgesDir: normalizedCartridgesDir } : {}
} : void 0;
function withBaseDir(suffix) {
return normalizedCartridgesDir === "/" ? `/${suffix}` : `${normalizedCartridgesDir}/${suffix}`;
}
return [{
files: files ?? [withBaseDir("**/*.{js,ds}")],
ignores: ignores ?? [withBaseDir("*/cartridge/client/**"), withBaseDir("*/cartridge/static/**")],
languageOptions: {
sourceType: "commonjs",
globals: {
...globals.commonjs,
...sfccGlobals
}
},
plugins: {
es: fixupPluginRules(es),
sfcc,
sitegenesis
},
...sfccSettings === void 0 ? {} : { settings: { sfcc: sfccSettings } },
rules
}];
}
/** Shareable config for SFCC projects */
const recommended = createRecommendedConfig();
//#endregion
//#region src/index.ts
const configs = { recommended };
const plugins = {
sfcc,
sitegenesis
};
const eslintConfigSfcc = {
configs,
plugins
};
//#endregion
export { configs, createRecommendedConfig, eslintConfigSfcc as default, plugins, recommended, sfcc, sitegenesis };