Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/core/docs/functions/useComponentCollector.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,34 @@

> **useComponentCollector**(`context`, `hint`, `options`): `object`

Get a ctx and listeners for the rule to collect function components

## Parameters

### context

`Readonly`\<`RuleContext`\<`string`, readonly `unknown`[]\>\>

The ESLint rule context

### hint

`bigint` = `DEFAULT_COMPONENT_HINT`

The hint to use

### options

[`ComponentCollectorOptions`](../interfaces/ComponentCollectorOptions.md) = `{}`

The options to use

## Returns

`object`

The component collector

### ctx

> **ctx**: `object`
Expand Down
4 changes: 4 additions & 0 deletions packages/core/docs/functions/useComponentCollectorLegacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@

> **useComponentCollectorLegacy**(): `object`

Get a ctx and listeners for the rule to collect class components

## Returns

`object`

The context and listeners for the rule

### ctx

> **ctx**: `object`
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/component/component-collector-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { ERClassComponentFlag } from "./component-flag";
import type { ERClassComponent } from "./component-semantic-node";
import { isClassComponent, isPureComponent } from "./is";

/**
* Get a ctx and listeners for the rule to collect class components
* @returns The context and listeners for the rule
*/
export function useComponentCollectorLegacy() {
const components = new Map<string, ERClassComponent>();

Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/component/component-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ export interface ComponentCollectorOptions {
// dprint-ignore
const displayNameAssignmentSelector = "AssignmentExpression[type][operator='='][left.type='MemberExpression'][left.property.name='displayName']";

/**
* Get a ctx and listeners for the rule to collect function components
* @param context The ESLint rule context
* @param hint The hint to use
* @param options The options to use
* @returns The component collector
*/
export function useComponentCollector(
context: RuleContext,
hint = DEFAULT_COMPONENT_HINT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default createRule<[], MessageID>({
return {
JSXElement(node) {
const attributes = node.openingElement.attributes;
const attribute = JSX.getAttributeNode(
const attribute = JSX.getAttribute(
"dangerouslySetInnerHTML",
context.sourceCode.getScope(node),
attributes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default createRule<[], MessageID>({
return {
JSXElement(node) {
const [elementNameOnJsx, elementNameOnDom] = getElementNameOnJsxAndDom(
node.openingElement,
node,
context,
polymorphicPropName,
additionalComponents,
Expand All @@ -46,16 +46,15 @@ export default createRule<[], MessageID>({
const customComponent = findCustomComponent(elementNameOnJsx, additionalComponents);
const customComponentProp = findCustomComponentProp("type", customComponent?.attributes ?? []);
const propNameOnJsx = customComponentProp?.name ?? "type";
const attributeNode = JSX.getAttributeNode(
const attributeNode = JSX.getAttribute(
propNameOnJsx,
elementScope,
node.openingElement.attributes,
);
if (attributeNode != null) {
const attributeScope = context.sourceCode.getScope(attributeNode);
const attributeStaticValue = JSX.getAttributeStaticValue(attributeNode, attributeScope);
const attributeStringValue = JSX.toResolvedAttributeValue(propNameOnJsx, attributeStaticValue);
if (typeof attributeStringValue !== "string") {
const attributeValue = JSX.getAttributeValue(propNameOnJsx, attributeNode, attributeScope);
if (attributeValue.kind === "some" && typeof attributeValue.value !== "string") {
context.report({
messageId: "noMissingButtonType",
node: attributeNode,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { _ } from "@eslint-react/eff";
import * as JSX from "@eslint-react/jsx";
import type { RuleFeature } from "@eslint-react/shared";
import { getSettingsFromContext } from "@eslint-react/shared";
Expand Down Expand Up @@ -33,7 +32,7 @@ const validTypes = [
"allow-top-navigation-to-custom-protocols",
] as const;

function hasValidSandBox(value: string | _) {
function hasValidSandBox(value: unknown) {
return typeof value === "string"
&& value
.split(" ")
Expand All @@ -60,7 +59,7 @@ export default createRule<[], MessageID>({
return {
JSXElement(node) {
const [elementNameOnJsx, elementNameOnDom] = getElementNameOnJsxAndDom(
node.openingElement,
node,
context,
polymorphicPropName,
additionalComponents,
Expand All @@ -72,16 +71,15 @@ export default createRule<[], MessageID>({
const customComponent = findCustomComponent(elementNameOnJsx, additionalComponents);
const customComponentProp = findCustomComponentProp("sandbox", customComponent?.attributes ?? []);
const propNameOnJsx = customComponentProp?.name ?? "sandbox";
const attributeNode = JSX.getAttributeNode(
const attributeNode = JSX.getAttribute(
propNameOnJsx,
elementScope,
node.openingElement.attributes,
);
if (attributeNode != null) {
const attributeScope = context.sourceCode.getScope(attributeNode);
const attributeStaticValue = JSX.getAttributeStaticValue(attributeNode, attributeScope);
const attributeStringValue = JSX.toResolvedAttributeValue(propNameOnJsx, attributeStaticValue);
if (hasValidSandBox(attributeStringValue)) return;
const attributeValue = JSX.getAttributeValue(propNameOnJsx, attributeNode, attributeScope);
if (attributeValue.kind === "some" && hasValidSandBox(attributeValue.value)) return;
context.report({
messageId: "noMissingIframeSandbox",
node: attributeNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export default createRule<[], MessageID>({
name: RULE_NAME,
create(context) {
return {
JSXOpeningElement(node) {
JSXElement(node) {
const name = JSX.getElementName(node);
if (typeof name !== "string" || !name.includes(":")) {
return;
}
context.report({
messageId: "noNamespace",
node,
node: node.openingElement.name,
data: {
name,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as JSX from "@eslint-react/jsx";
import type { RuleFeature } from "@eslint-react/shared";
import { RE_JAVASCRIPT_PROTOCOL } from "@eslint-react/shared";
import * as VAR from "@eslint-react/var";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
import type { CamelCase } from "string-ts";

Expand Down Expand Up @@ -39,10 +38,9 @@ export default createRule<[], MessageID>({
return;
}
const attributeScope = context.sourceCode.getScope(node);
const attributeValue = JSX.getAttributeStaticValue(node, attributeScope);
const attributeValueResolved = VAR.toResolved(attributeValue).value;
if (typeof attributeValueResolved !== "string") return;
if (RE_JAVASCRIPT_PROTOCOL.test(attributeValueResolved)) {
const attributeValue = JSX.getAttributeValue(JSX.toString(node.name), node, attributeScope);
if (attributeValue.kind === "none" || typeof attributeValue.value !== "string") return;
if (RE_JAVASCRIPT_PROTOCOL.test(attributeValue.value)) {
context.report({
messageId: "noScriptUrl",
node: node.value,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { _ } from "@eslint-react/eff";
import * as JSX from "@eslint-react/jsx";
import type { RuleFeature } from "@eslint-react/shared";
import { getSettingsFromContext } from "@eslint-react/shared";
Expand All @@ -18,8 +17,8 @@ const unsafeSandboxValues = [
["allow-scripts", "allow-same-origin"],
] as const;

function hasNoneOrSafeSandbox(value: string | _) {
if (value == null) return true;
function hasSafeSandbox(value: unknown) {
if (typeof value !== "string") return false;
return !unsafeSandboxValues.some((values) => {
return values.every((v) => value.includes(v));
});
Expand All @@ -45,7 +44,7 @@ export default createRule<[], MessageID>({
return {
JSXElement(node) {
const [elementNameOnJsx, elementNameOnDom] = getElementNameOnJsxAndDom(
node.openingElement,
node,
context,
polymorphicPropName,
additionalComponents,
Expand All @@ -57,23 +56,24 @@ export default createRule<[], MessageID>({
const customComponent = findCustomComponent(elementNameOnJsx, additionalComponents);
const customComponentProp = findCustomComponentProp("sandbox", customComponent?.attributes ?? []);
const propNameOnJsx = customComponentProp?.name ?? "sandbox";
const attributeNode = JSX.getAttributeNode(
const attributeNode = JSX.getAttribute(
propNameOnJsx,
elementScope,
node.openingElement.attributes,
);
if (attributeNode != null) {
const attributeScope = context.sourceCode.getScope(attributeNode);
const attributeStaticValue = JSX.getAttributeStaticValue(attributeNode, attributeScope);
const attributeStringValue = JSX.toResolvedAttributeValue(propNameOnJsx, attributeStaticValue);
if (hasNoneOrSafeSandbox(attributeStringValue)) return;
context.report({
messageId: "noUnsafeIframeSandbox",
node: attributeNode,
});
return;
const attributeValue = JSX.getAttributeValue(propNameOnJsx, attributeNode, attributeScope);
if (attributeValue.kind === "some" && !hasSafeSandbox(attributeValue.value)) {
context.report({
messageId: "noUnsafeIframeSandbox",
node: attributeNode,
});
return;
}
}
if (!hasNoneOrSafeSandbox(customComponentProp?.defaultValue)) {
if (customComponentProp?.defaultValue == null) return;
if (!hasSafeSandbox(customComponentProp.defaultValue)) {
context.report({
messageId: "noUnsafeIframeSandbox",
node,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { _ } from "@eslint-react/eff";
import { _ } from "@eslint-react/eff";
import * as JSX from "@eslint-react/jsx";
import type { RuleFeature } from "@eslint-react/shared";
import { getSettingsFromContext } from "@eslint-react/shared";
Expand Down Expand Up @@ -49,7 +49,7 @@ export default createRule<[], MessageID>({
return {
JSXElement(node: TSESTree.JSXElement) {
const [elementNameOnJsx, elementNameOnDom] = getElementNameOnJsxAndDom(
node.openingElement,
node,
context,
polymorphicPropName,
additionalComponents,
Expand All @@ -58,27 +58,30 @@ export default createRule<[], MessageID>({
const elementScope = context.sourceCode.getScope(node);
const customComponent = findCustomComponent(elementNameOnJsx, additionalComponents);

const getAttributeValue = (name: string) => {
const getAttributeStringValue = (name: string) => {
const customComponentProp = findCustomComponentProp(name, customComponent?.attributes ?? []);
const propNameOnJsx = customComponentProp?.name ?? name;
const attributeNode = JSX.getAttributeNode(
const attributeNode = JSX.getAttribute(
propNameOnJsx,
elementScope,
node.openingElement.attributes,
);
if (attributeNode == null) return customComponentProp?.defaultValue;
const attributeScope = context.sourceCode.getScope(attributeNode);
const attributeStaticValue = JSX.getAttributeStaticValue(attributeNode, attributeScope);
return JSX.toResolvedAttributeValue(propNameOnJsx, attributeStaticValue);
const attributeValue = JSX.getAttributeValue(propNameOnJsx, attributeNode, attributeScope);
if (attributeValue.kind === "some" && typeof attributeValue.value === "string") {
return attributeValue.value;
}
return _;
};

if (getAttributeValue("target") !== "_blank") {
if (getAttributeStringValue("target") !== "_blank") {
return;
}
if (!isExternalLinkLike(getAttributeValue("href"))) {
if (!isExternalLinkLike(getAttributeStringValue("href"))) {
return;
}
if (isSafeRel(getAttributeValue("rel"))) {
if (isSafeRel(getAttributeStringValue("rel"))) {
return;
}
context.report({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default createRule<[], MessageID>({
create(context) {
return {
JSXElement(node) {
const elementName = JSX.getElementName(node.openingElement);
const elementName = JSX.getElementName(node);
if (elementName.length === 0 || !voidElements.has(elementName)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as JSX from "@eslint-react/jsx";
import type { CustomComponentNormalized, RuleContext } from "@eslint-react/shared";
import * as VAR from "@eslint-react/var";
import type { TSESTree } from "@typescript-eslint/types";

export function getElementNameOnJsxAndDom(
node: TSESTree.JSXOpeningElement,
node: TSESTree.JSXElement,
context: RuleContext,
polymorphicPropName?: string,
additionalComponents: CustomComponentNormalized[] = [],
Expand All @@ -19,15 +18,15 @@ export function getElementNameOnJsxAndDom(
if (polymorphicPropName == null) return [name, name];
// Get the component name using the `settings["react-x"].polymorphicPropName` setting
const initialScope = context.sourceCode.getScope(node);
const attributeNode = JSX.getAttributeNode(
const attributeNode = JSX.getAttribute(
polymorphicPropName,
initialScope,
node.attributes,
node.openingElement.attributes,
);
if (attributeNode == null) return [name, name];
const polymorphicPropValue = VAR.toResolved(JSX.getAttributeStaticValue(attributeNode, initialScope)).value;
if (typeof polymorphicPropValue === "string") {
return [name, polymorphicPropValue];
const polymorphicPropValue = JSX.getAttributeValue(polymorphicPropName, attributeNode, initialScope);
if (polymorphicPropValue.kind === "some" && typeof polymorphicPropValue.value === "string") {
return [name, polymorphicPropValue.value];
}
return [name, name];
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function isSetFunctionCall(context: RuleContext, settings: ESLintReactSet
return false;
}
const indexScope = context.sourceCode.getScope(node);
const indexValue = VAR.toResolved({
const indexValue = VAR.toStaticValue({
kind: "lazy",
node: index,
initialScope: indexScope,
Expand All @@ -45,7 +45,7 @@ export function isSetFunctionCall(context: RuleContext, settings: ESLintReactSet
}
const property = node.callee.property;
const propertyScope = context.sourceCode.getScope(node);
const propertyValue = VAR.toResolved({
const propertyValue = VAR.toStaticValue({
kind: "lazy",
node: property,
initialScope: propertyScope,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export default createRule<Options, MessageID>({
...collector.listeners,
...collectorLegacy.listeners,
JSXOpeningElement(node) {
const name = JSX.getElementName(node);
const name = JSX.getElementName(node.parent);
if (/^[a-z]/u.test(name)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function getOptions(node: TSESTree.CallExpressionArgument, initialScope: Scope):
break;
}
default: {
v = VAR.toResolved({ kind: "lazy", node: value, initialScope }).value;
v = VAR.toStaticValue({ kind: "lazy", node: value, initialScope }).value;
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default createRule<[], MessageID>({
create(context) {
return {
JSXElement(node) {
const attribute = JSX.getAttributeNode(
const attribute = JSX.getAttribute(
"children",
context.sourceCode.getScope(node),
node.openingElement.attributes,
Expand Down
Loading
Loading