Add getJSXAttribute, hasJSXAttribute and isJSXElement methods to js-ast-utils
ADD
packages/@romejs/js-ast-utils/getJSXAttribute.ts
+import {AnyExpression, JSXAttribute, JSXElement} from "@romejs/js-ast";
+
+function isEmptyAttributeValue(
+ node: NonNullable<JSXAttribute["value"]> | AnyExpression,
+): boolean {
+ switch (node.type) {
+ case "StringLiteral":
+ return node.value === "";
+
+ case "JSXExpressionContainer":
+ return isEmptyAttributeValue(node.expression);
+
+ case "ReferenceIdentifier":
+ return node.name === "undefined";
+
+ case "JSXEmptyExpression":
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+export default function getJSXAttribute(
+ tag: JSXElement,
+ name: string,
+ allowEmpty: boolean = false,
+): JSXAttribute | undefined {
+ for (const attr of tag.attributes) {
+ if (attr.type === "JSXAttribute" && attr.name.name === name) {
+ const {value} = attr;
+
+ if (value !== undefined && !allowEmpty && isEmptyAttributeValue(value)) {
+ return undefined;
+ }
+
+ return attr;
+ }
+ }
+ return undefined;
+}

ADD
packages/@romejs/js-ast-utils/hasJSXAttribute.ts
+import {JSXElement} from "@romejs/js-ast";
+import getJSXAttribute from "./getJSXAttribute";
+
+export default function hasJSXAttribute(
+ tag: JSXElement,
+ name: string,
+ allowEmpty: boolean = false,
+): boolean {
+ return getJSXAttribute(tag, name, allowEmpty) !== undefined;
+}

MODIFY
packages/@romejs/js-ast-utils/index.ts
packages/@romejs/js-ast-utils/index.ts
@@ -38,3 +38,6 @@ export {default as template} from "./template";
export {default as valueToNode} from "./valueToNode";
export {default as createPropertyKey} from "./createPropertyKey";
export {default as createMemberProperty} from "./createMemberProperty";
+export {default as hasJSXAttribute} from "./hasJSXAttribute";
+export {default as getJSXAttribute} from "./getJSXAttribute";
+export {default as isJSXElement} from "./isJSXElement";

ADD
packages/@romejs/js-ast-utils/isJSXElement.ts
+import {AnyNode, JSXElement} from "@romejs/js-ast";
+import doesNodeMatchPattern from "./doesNodeMatchPattern";
+
+export default function isJSXElement(
+ node: AnyNode,
+ name: string,
+): node is JSXElement {
+ return node.type === "JSXElement" && doesNodeMatchPattern(node.name, name);
+}

MODIFY
packages/@romejs/js-compiler/lint/rules/react/jsxA11yHTMLHasLang.ts
packages/@romejs/js-compiler/lint/rules/react/jsxA11yHTMLHasLang.ts
import {descriptions} from "@romejs/diagnostics";
import {AnyNode} from "@romejs/js-ast";
import {Path} from "@romejs/js-compiler";
+import {hasJSXAttribute, isJSXElement} from "@romejs/js-ast-utils";
function jsxHTMLMissingLang(node: AnyNode) {
- return (
- node.type === "JSXElement" &&
- node.name.type === "JSXIdentifier" &&
- node.name.name === "html" &&
- !node.attributes.some((attribute) =>
- attribute.type === "JSXAttribute" &&
- attribute.name.name === "lang" &&
- attribute.value &&
- ((attribute.value.type === "StringLiteral" &&
- attribute.value.value.length > 0) ||
- (attribute.value.type === "JSXExpressionContainer" &&
- ((attribute.value.expression.type === "ReferenceIdentifier" &&
- attribute.value.expression.name !== "undefined") ||
- (attribute.value.expression.type === "StringLiteral" &&
- attribute.value.expression.value.length > 0))))
- )
- );
+ return isJSXElement(node, "html") && !hasJSXAttribute(node, "lang");
}
export default {

MODIFY
packages/@romejs/js-compiler/lint/rules/react/jsxA11yIframeHasTitle.ts
packages/@romejs/js-compiler/lint/rules/react/jsxA11yIframeHasTitle.ts
*/
import {descriptions} from "@romejs/diagnostics";
+import {hasJSXAttribute, isJSXElement} from "@romejs/js-ast-utils";
import {AnyNode} from "@romejs/js-ast";
import {Path} from "@romejs/js-compiler";
function jsxIframeMissingTitle(node: AnyNode) {
- return (
- node.type === "JSXElement" &&
- node.name.type === "JSXIdentifier" &&
- node.name.name === "iframe" &&
- !node.attributes.some((attribute) =>
- attribute.type === "JSXAttribute" &&
- attribute.name.name === "title" &&
- attribute.value &&
- ((attribute.value.type === "StringLiteral" &&
- attribute.value.value.length > 0) ||
- (attribute.value.type === "JSXExpressionContainer" &&
- ((attribute.value.expression.type === "ReferenceIdentifier" &&
- attribute.value.expression.name !== "undefined") ||
- (attribute.value.expression.type === "StringLiteral" &&
- attribute.value.expression.value.length > 0))))
- )
- );
+ return isJSXElement(node, "iframe") && !hasJSXAttribute(node, "title");
}
export default {

MODIFY
packages/@romejs/js-compiler/lint/rules/react/jsxA11yImgRedundantAlt.ts
packages/@romejs/js-compiler/lint/rules/react/jsxA11yImgRedundantAlt.ts
import {descriptions} from "@romejs/diagnostics";
import {AnyNode} from "@romejs/js-ast";
import {Path} from "@romejs/js-compiler";
+import {getJSXAttribute, isJSXElement} from "@romejs/js-ast-utils";
function jsxImgRedundantAlt(node: AnyNode) {
+ if (!isJSXElement(node, "img")) {
+ return false;
+ }
+
+ const attr = getJSXAttribute(node, "alt");
return (
- node.type === "JSXElement" &&
- node.name.type === "JSXIdentifier" &&
- node.name.name === "img" &&
- node.attributes.some((attribute) =>
- attribute.type === "JSXAttribute" &&
- attribute.name.name === "alt" &&
- attribute.value &&
- attribute.value.type === "StringLiteral" &&
- /(image)|(picture)|(photo)/i.test(attribute.value.value)
- )
+ attr !== undefined &&
+ attr.value &&
+ attr.value.type === "StringLiteral" &&
+ /(image)|(picture)|(photo)/i.test(attr.value.value)
);
}

MODIFY
packages/@romejs/js-compiler/lint/rules/react/jsxA11yNoTargetBlank.test.md
packages/@romejs/js-compiler/lint/rules/react/jsxA11yNoTargetBlank.test.md
### `0: formatted`
```
-<a href="http://external.link" target="_blank">child</a>;
+<a href='http://external.link' target='_blank'>child</a>;
```
### `1: formatted`
```
-<a href={dynamicLink} target="_blank">child</a>;
+<a href={dynamicLink} target='_blank'>child</a>;
```
### `2: formatted`
```
-<p href="http://external.link" target="_blank">child</p>;
+<p href='http://external.link' target='_blank'>child</p>;
```
### `3: formatted`
```
-<a href="http://external.link" rel="noreferrer" target="_blank">child</a>;
+<a href='http://external.link' rel='noreferrer' target='_blank'>child</a>;
```
### `4: formatted`
```
-<a href="http://external.link" rel="noopener noreferrer" target="_blank">child</a>;
+<a href='http://external.link' rel='noopener noreferrer' target='_blank'>child</a>;
```
### `5: formatted`
```
-<a href="relative/link" rel="noreferrer" target="_blank">child</a>;
+<a href='relative/link' rel='noreferrer' target='_blank'>child</a>;
```
### `6: formatted`
```
-<a href="/absolute/link" rel="noreferrer" target="_blank">child</a>;
+<a href='/absolute/link' rel='noreferrer' target='_blank'>child</a>;
```

MODIFY
packages/@romejs/js-compiler/lint/rules/react/jsxA11yNoTargetBlank.ts
packages/@romejs/js-compiler/lint/rules/react/jsxA11yNoTargetBlank.ts
import {descriptions} from "@romejs/diagnostics";
import {AnyNode} from "@romejs/js-ast";
import {Path} from "@romejs/js-compiler";
+import {isJSXElement} from "@romejs/js-ast-utils";
function jsxAnchorHasBlankTarget(node: AnyNode) {
return (
- node.type === "JSXElement" &&
- node.name.type === "JSXIdentifier" &&
- node.name.name === "a" &&
+ isJSXElement(node, "a") &&
node.attributes.some((attribute) =>
attribute.type === "JSXAttribute" &&
attribute.name.name === "target" &&
@@ -19,9 +18,7 @@ function jsxAnchorHasBlankTarget(node: AnyNode) {
function jsxAnchorHasNoReferrer(node: AnyNode) {
return (
- node.type === "JSXElement" &&
- node.name.type === "JSXIdentifier" &&
- node.name.name === "a" &&
+ isJSXElement(node, "a") &&
node.attributes.some((attribute) =>
attribute.type === "JSXAttribute" &&
attribute.name.name === "rel" &&
@@ -34,9 +31,7 @@ function jsxAnchorHasNoReferrer(node: AnyNode) {
function jsxAnchorHasExternalLink(node: AnyNode) {
return (
- node.type === "JSXElement" &&
- node.name.type === "JSXIdentifier" &&
- node.name.name === "a" &&
+ isJSXElement(node, "a") &&
node.attributes.some((attribute) =>
attribute.type === "JSXAttribute" &&
attribute.name.name === "href" &&

Add getJSXAttribute, hasJSXAttribute and isJSXElement methods to js-ast-utils