@pega/custom-dx-components
Version:
Utility for building custom UI components
104 lines (90 loc) • 3.29 kB
JavaScript
import fs from 'fs';
import path from 'path';
/** Cache: component directory → parsed config.json (or null) */
const configCache = new Map();
/**
* Walks up the directory tree from `startDir` to find a `config.json` file,
* stopping at the project root (`process.cwd()`) to avoid picking up unrelated configs.
* Results are cached per directory to minimise repeated filesystem reads.
*/
function findComponentConfig(startDir) {
if (configCache.has(startDir)) {
return configCache.get(startDir);
}
const projectRoot = path.resolve(process.cwd());
let dir = path.resolve(startDir);
while (dir.startsWith(projectRoot)) {
const candidate = path.join(dir, 'config.json');
if (fs.existsSync(candidate)) {
let parsed = null;
try {
parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
} catch {
// ignore malformed config
}
configCache.set(startDir, parsed);
return parsed;
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
configCache.set(startDir, null);
return null;
}
/** @type {import('eslint').Rule.RuleModule} */
const rule = {
meta: {
type: 'problem',
docs: {
description: 'Disallow PCore and getPConnect usage in Presentation components',
category: 'Pega DX Components',
recommended: true
},
messages: {
noPCore:
'PCore must not be used in Presentation components (type="Presentation" in config.json). These are presentation-only components with no platform API access.',
noGetPConnect:
'getPConnect must not be used in Presentation components (type="Presentation" in config.json). These are presentation-only components with no platform API access.'
},
schema: []
},
create(context) {
const filename = context.filename ?? context.getFilename();
const fileDir = path.dirname(filename);
const config = findComponentConfig(fileDir);
if (!config || config.type !== 'Presentation') {
return {};
}
return {
// Flag `PCore` used as a standalone identifier (e.g. PCore.getConstants())
Identifier(node) {
if (node.name === 'PCore') {
context.report({ node, messageId: 'noPCore' });
}
},
// Flag `getPConnect` in destructuring: const { getPConnect } = props
// and as a property access: props.getPConnect()
Property(node) {
if (node.key && node.key.name === 'getPConnect') {
context.report({ node, messageId: 'noGetPConnect' });
}
},
// Flag `getPConnect` as a member access: props.getPConnect or props['getPConnect']
MemberExpression(node) {
const prop = node.property;
const isGetPConnect = node.computed ? prop.type === 'Literal' && prop.value === 'getPConnect' : prop.name === 'getPConnect';
if (isGetPConnect) {
context.report({ node, messageId: 'noGetPConnect' });
}
},
// Flag `getPConnect` called directly as a standalone function: getPConnect()
CallExpression(node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'getPConnect') {
context.report({ node, messageId: 'noGetPConnect' });
}
}
};
}
};
export default rule;