Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
125 lines
4.0 KiB
JavaScript
125 lines
4.0 KiB
JavaScript
|
|
export default {
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: 'Ensure required props are provided to components',
|
|
category: 'Possible Errors',
|
|
recommended: true,
|
|
},
|
|
schema: [],
|
|
},
|
|
create(context) {
|
|
const sourceCode = context.getSourceCode();
|
|
|
|
// Collect local identifiers bound to our UI Button component
|
|
// We scope the rule primarily to Buttons imported from .../ui/Button
|
|
const buttonLocalNames = new Set();
|
|
|
|
// Updated regex to catch all Button import patterns including aliases
|
|
const buttonPathRegex = /(@\/components\/ui\/[Bb]utton|[\\/](ui[\\/])?[Bb]utton)(\.tsx?)?$/i;
|
|
|
|
for (const node of sourceCode.ast.body || []) {
|
|
if (node.type === 'ImportDeclaration') {
|
|
const src = node.source && node.source.value;
|
|
if (
|
|
typeof src === 'string' &&
|
|
(
|
|
buttonPathRegex.test(src) ||
|
|
src === 'server/src/components/ui/Button' ||
|
|
src === '../../components/ui/Button' ||
|
|
src === '../components/ui/Button' ||
|
|
src === '../ui/Button' ||
|
|
src === './ui/Button' ||
|
|
src === '@/components/ui/button' ||
|
|
src === '@/components/ui/Button'
|
|
)
|
|
) {
|
|
for (const spec of node.specifiers || []) {
|
|
if (spec.type === 'ImportSpecifier' && spec.imported && spec.imported.name === 'Button') {
|
|
buttonLocalNames.add(spec.local.name);
|
|
} else if (spec.type === 'ImportDefaultSpecifier') {
|
|
buttonLocalNames.add(spec.local.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isTargetButton(openingEl) {
|
|
if (!openingEl || !openingEl.name) return false;
|
|
if (openingEl.name.type === 'JSXIdentifier') {
|
|
const name = openingEl.name.name;
|
|
// If we detected specific imports, prefer those. Otherwise fall back to literal "Button"
|
|
return buttonLocalNames.size > 0 ? buttonLocalNames.has(name) : name === 'Button';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function hasIdAttribute(openingEl) {
|
|
return (openingEl.attributes || []).some(
|
|
(attr) => attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'id'
|
|
);
|
|
}
|
|
|
|
function containsIdProperty(objExpr) {
|
|
if (!objExpr || objExpr.type !== 'ObjectExpression') return false;
|
|
return (objExpr.properties || []).some((p) => {
|
|
if (p.type !== 'Property') return false;
|
|
const key = p.key;
|
|
return (key.type === 'Identifier' && key.name === 'id') ||
|
|
(key.type === 'Literal' && key.value === 'id');
|
|
});
|
|
}
|
|
|
|
function hasIdViaSpread(openingEl) {
|
|
return (openingEl.attributes || []).some((attr) => {
|
|
if (attr.type !== 'JSXSpreadAttribute') return false;
|
|
const arg = attr.argument;
|
|
if (!arg) return false;
|
|
|
|
// Accept {...withDataAutomationId(...)} as satisfying the requirement,
|
|
// since that utility injects an 'id' (or derives one) consistently.
|
|
if (
|
|
arg.type === 'CallExpression' &&
|
|
arg.callee &&
|
|
arg.callee.type === 'Identifier' &&
|
|
arg.callee.name === 'withDataAutomationId'
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
// Also accept direct object spreads that contain an 'id' key: {...{ id: '...' }}
|
|
if (arg.type === 'ObjectExpression' && containsIdProperty(arg)) {
|
|
return true;
|
|
}
|
|
|
|
// Accept spreads from variables ending with 'Props' (e.g., filterButtonProps)
|
|
// These are typically from useAutomationIdAndRegister hook
|
|
if (
|
|
arg.type === 'Identifier' &&
|
|
(arg.name.endsWith('Props') || arg.name.endsWith('ButtonProps'))
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
return {
|
|
JSXOpeningElement(node) {
|
|
if (!isTargetButton(node)) return;
|
|
|
|
const ok = hasIdAttribute(node) || hasIdViaSpread(node);
|
|
if (!ok) {
|
|
context.report({
|
|
node,
|
|
message: 'Button component requires an id prop',
|
|
});
|
|
}
|
|
},
|
|
};
|
|
},
|
|
};
|