scope

Try in Playground
jsx-a11y

oscar143

Best PracticeError

0

No tags

No CWE or CVE

The scope prop should be used only on <th> elements.

Accessibility guidelines

  • WCAG 1.3.1
  • WCAG 4.1.1

Resources

Ast Rule: html element


scope

How to write a rule
const dom = [
  ['a', {
    reserved: false,
  }],
  ['abbr', {
    reserved: false,
  }],
  ['acronym', {
    reserved: false,
  }],
  ['address', {
    reserved: false,
  }],
  ['applet', {
    reserved: false,
  }],
  ['area', {
    reserved: false,
  }],
  ['article', {
    reserved: false,
  }],
  ['aside', {
    reserved: false,
  }],
  ['audio', {
    reserved: false,
  }],
  ['b', {
    reserved: false,
  }],
  ['base', {
    reserved: true
  }],
  ['bdi', {
    reserved: false,
  }],
  ['bdo', {
    reserved: false,
  }],
  ['big', {
    reserved: false,
  }],
  ['blink', {
    reserved: false,
  }],
  ['blockquote', {
    reserved: false,
  }],
  ['body', {
    reserved: false,
  }],
  ['br', {
    reserved: false,
  }],
  ['button', {
    reserved: false,
  }],
  ['canvas', {
    reserved: false,
  }],
  ['caption', {
    reserved: false,
  }],
  ['center', {
    reserved: false,
  }],
  ['cite', {
    reserved: false,
  }],
  ['code', {
    reserved: false,
  }],
  ['col', {
    reserved: true
  }],
  ['colgroup', {
    reserved: true
  }],
  ['content', {
    reserved: false,
  }],
  ['data', {
    reserved: false,
  }],
  ['datalist', {
    reserved: false,
  }],
  ['dd', {
    reserved: false,
  }],
  ['del', {
    reserved: false,
  }],
  ['details', {
    reserved: false,
  }],
  ['dfn', {
    reserved: false,
  }],
  ['dialog', {
    reserved: false,
  }],
  ['dir', {
    reserved: false,
  }],
  ['div', {
    reserved: false,
  }],
  ['dl', {
    reserved: false,
  }],
  ['dt', {
    reserved: false,
  }],
  ['em', {
    reserved: false,
  }],
  ['embed', {
    reserved: false,
  }],
  ['fieldset', {
    reserved: false,
  }],
  ['figcaption', {
    reserved: false,
  }],
  ['figure', {
    reserved: false,
  }],
  ['font', {
    reserved: false,
  }],
  ['footer', {
    reserved: false,
  }],
  ['form', {
    reserved: false,
  }],
  ['frame', {
    reserved: false,
  }],
  ['frameset', {
    reserved: false,
  }],
  ['h1', {
    reserved: false,
  }],
  ['h2', {
    reserved: false,
  }],
  ['h3', {
    reserved: false,
  }],
  ['h4', {
    reserved: false,
  }],
  ['h5', {
    reserved: false,
  }],
  ['h6', {
    reserved: false,
  }],
  ['head', {
    reserved: true
  }],
  ['header', {
    reserved: false,
  }],
  ['hgroup', {
    reserved: false,
  }],
  ['hr', {
    reserved: false,
  }],
  ['html', {
    reserved: true
  }],
  ['i', {
    reserved: false,
  }],
  ['iframe', {
    reserved: false,
  }],
  ['img', {
    reserved: false,
  }],
  ['input', {
    reserved: false,
  }],
  ['ins', {
    reserved: false,
  }],
  ['kbd', {
    reserved: false,
  }],
  ['keygen', {
    reserved: false,
  }],
  ['label', {
    reserved: false,
  }],
  ['legend', {
    reserved: false,
  }],
  ['li', {
    reserved: false,
  }],
  ['link', {
    reserved: true
  }],
  ['main', {
    reserved: false,
  }],
  ['map', {
    reserved: false,
  }],
  ['mark', {
    reserved: false,
  }],
  ['marquee', {
    reserved: false,
  }],
  ['menu', {
    reserved: false,
  }],
  ['menuitem', {
    reserved: false,
  }],
  ['meta', {
    reserved: true
  }],
  ['meter', {
    reserved: false,
  }],
  ['nav', {
    reserved: false,
  }],
  ['noembed', {
    reserved: true
  }],
  ['noscript', {
    reserved: true
  }],
  ['object', {
    reserved: false,
  }],
  ['ol', {
    reserved: false,
  }],
  ['optgroup', {
    reserved: false,
  }],
  ['option', {
    reserved: false,
  }],
  ['output', {
    reserved: false,
  }],
  ['p', {
    reserved: false,
  }],
  ['param', {
    reserved: true
  }],
  ['picture', {
    reserved: true
  }],
  ['pre', {
    reserved: false,
  }],
  ['progress', {
    reserved: false,
  }],
  ['q', {
    reserved: false,
  }],
  ['rp', {
    reserved: false,
  }],
  ['rt', {
    reserved: false,
  }],
  ['rtc', {
    reserved: false,
  }],
  ['ruby', {
    reserved: false,
  }],
  ['s', {
    reserved: false,
  }],
  ['samp', {
    reserved: false,
  }],
  ['script', {
    reserved: true
  }],
  ['section', {
    reserved: false,
  }],
  ['select', {
    reserved: false,
  }],
  ['small', {
    reserved: false,
  }],
  ['source', {
    reserved: true
  }],
  ['spacer', {
    reserved: false,
  }],
  ['span', {
    reserved: false,
  }],
  ['strike', {
    reserved: false,
  }],
  ['strong', {
    reserved: false,
  }],
  ['style', {
    reserved: true
  }],
  ['sub', {
    reserved: false,
  }],
  ['summary', {
    reserved: false,
  }],
  ['sup', {
    reserved: false,
  }],
  ['table', {
    reserved: false,
  }],
  ['tbody', {
    reserved: false,
  }],
  ['td', {
    reserved: false,
  }],
  ['textarea', {
    reserved: false,
  }],
  ['tfoot', {
    reserved: false,
  }],
  ['th', {
    reserved: false,
  }],
  ['thead', {
    reserved: false,
  }],
  ['time', {
    reserved: false,
  }],
  ['title', {
    reserved: true
  }],
  ['tr', {
    reserved: false,
  }],
  ['track', {
    reserved: true
  }],
  ['tt', {
    reserved: false,
  }],
  ['u', {
    reserved: false,
  }],
  ['ul', {
    reserved: false,
  }],
  ['var', {
    reserved: false,
  }],
  ['video', {
    reserved: false,
  }],
  ['wbr', {
    reserved: false,
  }],
  ['xmp', {
    reserved: false,
  }],
];

const TYPES = {
  string: getPropStringValue,
  sequence: getPropSequenceValue,
  array: getPropArrayValue,
  object: getPropObjectValue,
  object_element: getPropObjectValue,
  functiondefinition: () => {},
};

function getTag(node) {
  if (node && node.tag) {
    return node.tag.value;
  }
}

function getProp(attributes = [], prop = "") {
  if (!prop) return;

  return attributes.find((attribute) => {
    if (attribute && attribute.name && attribute.name.value) {
      return attribute.name.value === prop;
    }
  });
}

function getPropStringValue(value) {
  if (value.value === "true") {
    return true;
  }

  if (value.value === "false") {
    return false;
  }

  return value.value.replace(/^\"/g, "").replace(/\"$/g, "");
}

function getPropArrayValue(value) {
  const array = [];
  const arrayElements = value.elements;

  if (arrayElements.length) {
    for (const element of arrayElements) {
      array.push(TYPES[element.astType](element));
    }
  }

  return array;
}

function getPropObjectValue(value) {
  const object = {};
  const objectElements = value.elements;

  if (objectElements.length) {
    for (const element of objectElements) {
      object[element.name.value] = TYPES[element.value.astType](element.value);
    }
  }

  return object;
}

function getPropSequenceValue(value) {
  if (value.elements.length) {
    const element = value.elements[0];

    if (element) {
      return TYPES[element.astType](element);
    }
  }
}

function extractValue(attribute, extractor) {
  if (attribute) {
    // Null valued attributes imply truthiness.
    // For example: <div aria-hidden />
    if (attribute.value === null) return true;

    return extractor(attribute.value);
  }
}

function getValue(value) {
  return TYPES[value.astType](value);
}

function getPropValue(attribute) {
  return extractValue(attribute, getValue);
}

function propHasValue(attribute) {
  const value = getPropValue(attribute);

  if (typeof value !== "boolean") {
    return !!value;
  }

  return false;
}

function isDom(tag) {
  return Boolean(dom.find(([def]) => def === tag));
}

function visit(node) {
  const tag = getTag(node);

  // Do not test higher level JSX components, as we do not know what
  // low-level DOM element this maps to.
  if (!isDom(tag)) return;

  if (tag === "th") return;

  if (getProp(node.attributes, "scope")) {

    const error = buildError(
      node.start.line,
      node.start.col,
      node.end.line,
      node.end.col,
      'Enforce `scope` prop is only used on `<th>` elements.',
      "ERROR",
      "BEST_PRACTICES"
    );

    addError(error);
  }
}

good.jsx

Expected test result: no error

<th scope="col" />
<th scope={scope} />

bad.jsx

Expected test result: has error

<div scope />
Add comment

Log in to add a comment


    Be the first one to leave a comment!

Codiga Logo
Codiga Hub
  • Rulesets
  • Playground
  • Snippets
  • Cookbooks
soc-2 icon

We are SOC-2 Compliance Certified

G2 high performer medal

Codiga – All rights reserved 2022.