feat: add react/no-danger-with-children lint rule
MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -112,6 +112,7 @@ type LintDiagnosticCategory =
| 'lint/noCommaOperator'
| 'lint/noCompareNegZero'
| 'lint/noCondAssign'
+ | 'lint/noDangerWithChildren'
| 'lint/noDebugger'
| 'lint/noDelete'
| 'lint/noDeleteVars'

MODIFY
packages/@romejs/diagnostics/descriptions.ts
packages/@romejs/diagnostics/descriptions.ts
@@ -325,6 +325,10 @@ export const descriptions = createMessages({
category: 'lint/stylePropObject',
message: '<emphasis>style</emphasis> property value must be an object.',
},
+ NO_DANGER_WITH_CHILDREN: {
+ category: 'lint/noDangerWithChildren',
+ message: 'Only set one of <emphasis>children</emphasis> or <emphasis>props.dangerouslySetInnerHTML</emphasis>.',
+ },
PENDING_FIXES: (
relativeFilename: string,
original: string,

MODIFY
packages/@romejs/js-compiler/lint/rules/index.ts
packages/@romejs/js-compiler/lint/rules/index.ts
@@ -26,6 +26,7 @@ import noChildrenProp from './react/noChildrenProp';
import noCommaOperator from './regular/noCommaOperator';
import noCompareNegZero from './regular/noCompareNegZero';
import noCondAssign from './regular/noCondAssign';
+import noDangerWithChildren from './react/noDangerWithChildren';
import noDebugger from './regular/noDebugger';
import noDelete from './regular/noDelete';
import noDeleteVars from './regular/noDeleteVars';
@@ -82,6 +83,7 @@ export const lintTransforms = [
noCommaOperator,
noCompareNegZero,
noCondAssign,
+ noDangerWithChildren,
noDebugger,
noDelete,
noDeleteVars,

ADD
packages/@romejs/js-compiler/lint/rules/react/noDangerWithChildren.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(
+ 'disallow children with dangerouslySetInnerHTML',
+ async (t) => {
+ await testLintMultiple(
+ t,
+ [
+ // INVALID
+ '<div dangerouslySetInnerHTML={{ __html: "HTML" }}>children</div>',
+ '<Invalid dangerouslySetInnerHTML={{ __html: "HTML" }}>children</Invalid>',
+ 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } }, "children")',
+ 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } }, ["children"])',
+ 'React.createElement("Invalid", { dangerouslySetInnerHTML: { __html: "HTML" } }, "children")',
+ 'React.createElement("Invalid", { dangerouslySetInnerHTML: { __html: "HTML" } }, ["children"])',
+ // VALID
+ '<div dangerouslySetInnerHTML={{ __html: "HTML" }} />',
+ '<Valid dangerouslySetInnerHTML={{ __html: "HTML" }} />',
+ '<div>children</div>',
+ '<Valid>children</Valid>',
+ 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } })',
+ 'React.createElement("Valid", { dangerouslySetInnerHTML: { __html: "HTML" } })',
+ 'React.createElement("div", {}, "children")',
+ 'React.createElement("div", {}, ["children"])',
+ 'React.createElement("Valid", {}, "children")',
+ 'React.createElement("Valid", {}, ["children"])',
+ ],
+ {category: 'lint/noDangerWithChildren'},
+ );
+ },
+);

ADD
packages/@romejs/js-compiler/lint/rules/react/noDangerWithChildren.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';
+import {doesNodeMatchPattern} from '@romejs/js-ast-utils';
+
+function jsxDangerWithChildren(node: AnyNode) {
+ if (node.type !== 'JSXElement') {
+ return false;
+ }
+
+ const hasAttribute = !!node.attributes.find((attribute) =>
+ attribute.type === 'JSXAttribute' &&
+ attribute.name.name === 'dangerouslySetInnerHTML'
+ );
+
+ return hasAttribute && node.children && node.children.length > 0;
+}
+
+function createElementDangerWithChildren(node: AnyNode): boolean {
+ if (node.type !== 'CallExpression') {
+ return false;
+ }
+
+ const propsArgument = node.arguments[node.arguments.length - 2];
+
+ return (
+ doesNodeMatchPattern(node.callee, 'React.createElement') &&
+ node.arguments.length === 3 &&
+ propsArgument.type === 'ObjectExpression' &&
+ propsArgument.properties.some((prop) =>
+ prop.type === 'ObjectProperty' &&
+ prop.key.type === 'StaticPropertyKey' &&
+ prop.key.value.type === 'Identifier' &&
+ prop.key.value.name === 'dangerouslySetInnerHTML'
+ )
+ );
+}
+
+export default {
+ name: 'noDangerWithChildren',
+
+ enter(path: Path): AnyNode {
+ const {node} = path;
+
+ if (jsxDangerWithChildren(node) || createElementDangerWithChildren(node)) {
+ path.context.addNodeDiagnostic(
+ node,
+ descriptions.LINT.NO_DANGER_WITH_CHILDREN,
+ );
+ }
+
+ return node;
+ },
+};

feat: add react/no-danger-with-children lint rule