feat: add jsx-a11y/html-has-lang lint rule
MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -102,6 +102,7 @@ type LintDiagnosticCategory =
| 'lint/getterReturn'
| 'lint/importDefaultBasename'
| 'lint/inconsiderateLanguage'
+ | 'lint/jsxA11yHTMLHasLang'
| 'lint/jsxKey'
| 'lint/jsxNoCommentText'
| 'lint/negationElse'

MODIFY
packages/@romejs/diagnostics/descriptions.ts
packages/@romejs/diagnostics/descriptions.ts
@@ -402,6 +402,10 @@ export const descriptions = createMessages({
category: 'lint/reactInJsxScope',
message: markup`<emphasis>"React"</emphasis> must be in scope when using JSX`,
},
+ REACT_JSX_A11Y_HTML_HAS_LANG: {
+ category: 'lint/jsxA11yHTMLHasLang',
+ message: markup`<emphasis>html</emphasis> elements must have a <emphasis>lang prop</emphasis>.`,
+ },
REACT_JSX_VOID_DOM_ELEMENTS_NO_CHILDREN: (
element: string,
properties: Array<string>,

MODIFY
packages/@romejs/js-compiler/lint/rules/index.ts
packages/@romejs/js-compiler/lint/rules/index.ts
@@ -16,6 +16,7 @@ import emptyMatches from './regular/emptyMatches';
import getterReturn from './regular/getterReturn';
import importDefaultBasename from './regular/importDefaultBasename';
import inconsiderateLanguage from './regular/inconsiderateLanguage';
+import jsxA11YHTMLHasLang from './react/jsxA11yHTMLHasLang';
import jsxKey from './react/jsxKey';
import jsxNoCommentText from './react/jsxNoCommentText';
import negationElse from './regular/negationElse';
@@ -75,6 +76,7 @@ export const lintTransforms = [
getterReturn,
importDefaultBasename,
inconsiderateLanguage,
+ jsxA11YHTMLHasLang,
jsxKey,
jsxNoCommentText,
negationElse,

ADD
packages/@romejs/js-compiler/lint/rules/react/jsxA11yHTMLHasLang.test.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {test} from 'rome';
+
+import {testLintMultiple} from '../testHelpers';
+
+test(
+ 'require a lang attribute on <html> JSX elements',
+ async (t) => {
+ await testLintMultiple(
+ t,
+ [
+ // INVALID
+ '<html></html>',
+ '<html {...props}></html>',
+ '<html lang=""></html>',
+ '<html lang={""}></html>',
+ '<html lang={undefined}></html>',
+ '<html lang={false}></html>',
+ '<html lang={true}></html>',
+ '<html lang={42}></html>',
+ // VALID
+ '<html lang="en"></html>',
+ '<html lang={language}></html>',
+ ],
+ {category: 'lint/jsxA11yHTMLHasLang'},
+ );
+ },
+);

ADD
packages/@romejs/js-compiler/lint/rules/react/jsxA11yHTMLHasLang.ts
+/**
+* Copyright (c) Facebook, Inc. and its affiliates.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+import {descriptions} from '@romejs/diagnostics';
+import {AnyNode} from '@romejs/js-ast';
+import {Path} from '@romejs/js-compiler';
+
+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))))
+ )
+ );
+}
+
+export default {
+ name: 'jsxA11yHTMLHasLang',
+
+ enter(path: Path): AnyNode {
+ const {node} = path;
+
+ if (jsxHTMLMissingLang(node)) {
+ path.context.addNodeDiagnostic(
+ node,
+ descriptions.LINT.REACT_JSX_A11Y_HTML_HAS_LANG,
+ );
+ }
+
+ return node;
+ },
+};

feat: add jsx-a11y/html-has-lang lint rule