sicua
Version:
A tool for analyzing project structure and dependencies
249 lines (248 loc) • 8.95 kB
JavaScript
;
/**
* Logic for matching JSX elements to applicable accessibility rules
* Provides efficient rule lookup and filtering based on element characteristics
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ElementMatcher = void 0;
const a11yRules_1 = require("../rules/a11yRules");
const jsxAnalysisUtils_1 = require("../utils/jsxAnalysisUtils");
const constants_1 = require("../constants");
class ElementMatcher {
/**
* Find all rules that apply to a given JSX element
*/
static getApplicableRules(element) {
const applicableRules = [];
for (const rule of Object.values(a11yRules_1.A11Y_RULES)) {
if (this.matchesSelector(element, rule.selector)) {
applicableRules.push(rule);
}
}
return applicableRules;
}
/**
* Create rule matches for an element with its applicable rules
*/
static createRuleMatches(element) {
const applicableRules = this.getApplicableRules(element);
return applicableRules.map((rule) => ({
rule,
element,
}));
}
/**
* Batch process multiple elements to find all rule matches
*/
static createRuleMatchesForElements(elements) {
const matches = [];
for (const element of elements) {
const elementMatches = this.createRuleMatches(element);
matches.push(...elementMatches);
}
return matches;
}
/**
* Check if an element matches a given selector
*/
static matchesSelector(element, selector) {
// Check tag name match
if (selector.tagName && element.tagName !== selector.tagName) {
return false;
}
// Check tag names array match
if (selector.tagNames && !selector.tagNames.includes(element.tagName)) {
return false;
}
// Check required props (all must be present)
if (selector.hasProps) {
for (const requiredProp of selector.hasProps) {
if (!jsxAnalysisUtils_1.JSXAnalysisUtils.hasProp(element, requiredProp)) {
return false;
}
}
}
// Check any prop (at least one must be present)
if (selector.hasAnyProp) {
if (!jsxAnalysisUtils_1.JSXAnalysisUtils.hasAnyProp(element, selector.hasAnyProp)) {
return false;
}
}
// Check custom matcher
if (selector.customMatcher) {
if (!selector.customMatcher(element)) {
return false;
}
}
return true;
}
/**
* Get rules that are specifically for HTML elements
*/
static getHTMLElementRules() {
return Object.values(a11yRules_1.A11Y_RULES).filter((rule) => {
const selector = rule.selector;
return ((selector.tagName && constants_1.HTML_TAGS.includes(selector.tagName)) ||
(selector.tagNames &&
selector.tagNames.some((tag) => constants_1.HTML_TAGS.includes(tag))));
});
}
/**
* Get rules that apply to React components (non-HTML elements)
*/
static getReactComponentRules() {
return Object.values(a11yRules_1.A11Y_RULES).filter((rule) => {
// Rules that check for props or custom matchers can apply to React components
return (rule.selector.hasProps ||
rule.selector.hasAnyProp ||
rule.selector.customMatcher ||
(!rule.selector.tagName && !rule.selector.tagNames));
});
}
/**
* Filter elements by type (HTML vs React components)
*/
static filterElementsByType(elements, type) {
return elements.filter((element) => {
if (type === "html") {
return jsxAnalysisUtils_1.JSXAnalysisUtils.isHTMLElement(element);
}
else {
return jsxAnalysisUtils_1.JSXAnalysisUtils.isReactComponent(element);
}
});
}
/**
* Get rules applicable to a specific WCAG level
*/
static getRulesForWCAGLevel(level) {
return a11yRules_1.A11yRuleManager.getRulesByWCAGLevel(level);
}
/**
* Get rules with specific severity level
*/
static getRulesForSeverity(severity) {
return a11yRules_1.A11yRuleManager.getRulesBySeverity(severity);
}
/**
* Find elements that match a specific rule
*/
static findElementsForRule(elements, ruleId) {
const rule = a11yRules_1.A11Y_RULES[ruleId];
if (!rule) {
return [];
}
return elements.filter((element) => this.matchesSelector(element, rule.selector));
}
/**
* Check if any elements in a collection match a rule
*/
static hasElementsForRule(elements, ruleId) {
const rule = a11yRules_1.A11Y_RULES[ruleId];
if (!rule) {
return false;
}
return elements.some((element) => this.matchesSelector(element, rule.selector));
}
/**
* Get rule matches grouped by rule ID
*/
static groupMatchesByRule(matches) {
const grouped = {};
for (const match of matches) {
const ruleId = match.rule.id;
if (!grouped[ruleId]) {
grouped[ruleId] = [];
}
grouped[ruleId].push(match);
}
return grouped;
}
/**
* Get rule matches grouped by element
*/
static groupMatchesByElement(matches) {
const grouped = new Map();
for (const match of matches) {
const element = match.element;
if (!grouped.has(element)) {
grouped.set(element, []);
}
grouped.get(element).push(match);
}
return grouped;
}
/**
* Find rules that have no matching elements (useful for coverage analysis)
*/
static findUnusedRules(elements) {
const unusedRules = [];
for (const rule of Object.values(a11yRules_1.A11Y_RULES)) {
const hasMatches = elements.some((element) => this.matchesSelector(element, rule.selector));
if (!hasMatches) {
unusedRules.push(rule);
}
}
return unusedRules;
}
/**
* Calculate rule coverage statistics
*/
static calculateRuleCoverage(elements) {
const totalRules = Object.keys(a11yRules_1.A11Y_RULES).length;
const unusedRules = this.findUnusedRules(elements);
const applicableRules = totalRules - unusedRules.length;
const coveragePercentage = totalRules > 0 ? (applicableRules / totalRules) * 100 : 0;
return {
totalRules,
applicableRules,
coveragePercentage,
unusedRules: unusedRules.map((rule) => rule.id),
};
}
/**
* Optimize rule matching by pre-filtering based on element characteristics
*/
static optimizedRuleMatching(elements) {
const matches = [];
// Group elements by tag name for efficient processing
const elementsByTag = new Map();
for (const element of elements) {
const tagName = element.tagName;
if (!elementsByTag.has(tagName)) {
elementsByTag.set(tagName, []);
}
elementsByTag.get(tagName).push(element);
}
// Process rules more efficiently by matching tag-specific rules first
for (const [tagName, tagElements] of elementsByTag) {
const relevantRules = a11yRules_1.A11yRuleManager.getRulesForElement(tagName);
for (const element of tagElements) {
for (const rule of relevantRules) {
if (this.matchesSelector(element, rule.selector)) {
matches.push({ rule, element });
}
}
}
}
// Process prop-based rules for all elements
const propBasedRules = Object.values(a11yRules_1.A11Y_RULES).filter((rule) => rule.selector.hasProps ||
rule.selector.hasAnyProp ||
rule.selector.customMatcher);
for (const element of elements) {
const elementProps = Object.keys(element.props);
const relevantPropRules = a11yRules_1.A11yRuleManager.getRulesForProps(elementProps);
for (const rule of relevantPropRules) {
if (this.matchesSelector(element, rule.selector)) {
// Avoid duplicate matches
const alreadyMatched = matches.some((match) => match.rule.id === rule.id && match.element === element);
if (!alreadyMatched) {
matches.push({ rule, element });
}
}
}
}
return matches;
}
}
exports.ElementMatcher = ElementMatcher;