Skip to content

fix: type getVariableValue helper #796

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
JSXFragment,
JSXOpeningElement,
MemberExpression,
Property,
SpreadElement,
} from "estree-jsx";

export function getAttribute(
Expand Down Expand Up @@ -35,37 +37,66 @@ export function getAnyAttribute(
return foundAttribute;
}

/**
* Attribute value and its type
*/
type Attribute =
| { type: "string"; value: string }
| {
type: "Literal";
value: string | number | bigint | boolean | RegExp | null | undefined;
}
| { type: "MemberExpression"; value: MemberExpression }
| { type: "ObjectExpression"; value: (Property | SpreadElement)[] }
| { type: "undefined"; value: undefined };

const UNDEFINED: Attribute = { type: "undefined", value: undefined };

/**
* Helper to get the raw value from a JSXAttribute value. If the JSXAttribute value is an Identifier, it tries to get the value of that variable. If the JSXAttribute value is a JSXExpressionContainer: {"value"}, it returns the inner content.
* MemberExpressions and ObjectExpressions are not parsed further.
* @param context Rule context
* @param node JSXAttribute value
* @returns Attribute in this form: { type: [specific type of the returned value], value: [actual value]}. Literal types are further processed, if their value is of type "string", type: "string" is returned, otherwise type: "Literal"
*/
export function getAttributeValue(
context: Rule.RuleContext,
node?: JSXAttribute["value"]
) {
): Attribute {
if (!node) {
return;
return UNDEFINED;
}

const valueType = node.type;

if (valueType === "Literal") {
return node.value;
if (typeof node.value === "string") {
return { type: "string", value: node.value };
}
return { type: "Literal", value: node.value };
}

if (valueType !== "JSXExpressionContainer") {
return;
return UNDEFINED;
}

if (node.expression.type === "Identifier") {
const variableScope = context.getSourceCode().getScope(node);
return getVariableValue(node.expression.name, variableScope, context);
}
if (node.expression.type === "MemberExpression") {
return getMemberExpression(node.expression);
return { type: "MemberExpression", value: node.expression };
}
if (node.expression.type === "Literal") {
return node.expression.value;
if (typeof node.expression.value === "string") {
return { type: "string", value: node.expression.value };
}
return { type: "Literal", value: node.expression.value };
}
if (node.expression.type === "ObjectExpression") {
return node.expression.properties;
return { type: "ObjectExpression", value: node.expression.properties };
}
return UNDEFINED;
}

export function getExpression(node?: JSXAttribute["value"]) {
Expand All @@ -78,15 +109,6 @@ export function getExpression(node?: JSXAttribute["value"]) {
}
}

function getMemberExpression(node: MemberExpression) {
if (!node) {
return;
}
const { object, property } = node;

return { object, property };
}

export function getVariableDeclaration(
name: string,
scope: Scope.Scope | null
Expand All @@ -103,22 +125,39 @@ export function getVariableDeclaration(
return undefined;
}

export function getVariableInit(
variableDeclaration: Scope.Variable | undefined
) {
if (!variableDeclaration || !variableDeclaration.defs.length) {
return;
}

const variableDefinition = variableDeclaration.defs[0];

if (variableDefinition.type !== "Variable") {
return;
}

return variableDefinition.node.init;
}

/**
* Helper to get the raw value of a variable, given by its name. Returns an Attribute object, similarly to getAttributeValue helper.
* @param name Variable name
* @param scope Scope where to look for the variable declaration
* @param context Rule context
* @returns Attribute in this form: { type: [specific type of the returned value], value: [actual value]}. Literal types are further processed, if their value is of type "string", type: "string" is returned, otherwise type: "Literal"
*/
export function getVariableValue(
name: string,
scope: Scope.Scope | null,
context: Rule.RuleContext
) {
): Attribute {
const variableDeclaration = getVariableDeclaration(name, scope);
if (!variableDeclaration) {
return;
}

const variableInit = variableDeclaration.defs.length
? variableDeclaration.defs[0].node.init
: undefined;
const variableInit = getVariableInit(variableDeclaration);

if (!variableInit) {
return;
return UNDEFINED;
}
if (variableInit.type === "Identifier") {
return getVariableValue(
Expand All @@ -128,12 +167,16 @@ export function getVariableValue(
);
}
if (variableInit.type === "Literal") {
return variableInit.value;
if (typeof variableInit.value === "string") {
return { type: "string", value: variableInit.value };
}
return { type: "Literal", value: variableInit.value };
}
if (variableInit.type === "MemberExpression") {
return getMemberExpression(variableInit);
return { type: "MemberExpression", value: variableInit };
}
if (variableInit.type === "ObjectExpression") {
return variableInit.properties;
return { type: "ObjectExpression", value: variableInit.properties };
}
return UNDEFINED;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Rule } from "eslint";
import { MemberExpression } from "estree-jsx";
import { getVariableValue } from ".";

/** Used to get a property name on an enum (MemberExpression). */
export function getEnumPropertyName(
context: Rule.RuleContext,
enumNode: MemberExpression
) {
if (enumNode.property.type === "Identifier") {
// E.g. const key = "key"; someEnum[key]
if (enumNode.computed) {
const scope = context.getSourceCode().getScope(enumNode);
const propertyName = enumNode.property.name;

return getVariableValue(propertyName, scope, context).value?.toString();
}
// E.g. someEnum.key
return enumNode.property.name;
}

// E.g. someEnum["key"]
if (enumNode.property.type === "Literal") {
return enumNode.property.value?.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Rule } from "eslint";
import { JSXAttribute } from "estree-jsx";
import { getVariableDeclaration } from "./JSXAttributes";
import { getVariableDeclaration, getVariableInit } from "./JSXAttributes";

/** Used to find the node where a prop value is initially assigned, to then be passed
* as a fixer function's nodeOrToken argument. Useful for when a prop may have an inline value, e.g. `<Comp prop="value" />`, or
Expand Down Expand Up @@ -41,6 +41,6 @@ function getJSXExpressionContainerValue(
scope
);

return variableDeclaration && variableDeclaration.defs[0].node.init;
return getVariableInit(variableDeclaration);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Rule } from "eslint";
import { Property, Identifier } from "estree-jsx";
import { getVariableDeclaration } from "./JSXAttributes";
import { Property } from "estree-jsx";
import { propertyNameMatches } from "./propertyNameMatches";

/** Can be used to run logic on the specified property of an ObjectExpression or
* only if the specified property exists.
Expand All @@ -14,26 +14,9 @@ export function getObjectProperty(
return;
}

const matchedProperty = properties.find((property) => {
const isIdentifier = property.key.type === "Identifier";
const { computed } = property;

// E.g. const key = "key"; {[key]: value}
if (isIdentifier && computed) {
const scope = context.getSourceCode().getScope(property);
const propertyName = (property.key as Identifier).name;
const propertyVariable = getVariableDeclaration(propertyName, scope);
return propertyVariable?.defs[0].node.init.value === name;
}
// E.g. {key: value}
if (isIdentifier && !computed) {
return (property.key as Identifier).name === name;
}
// E.g. {"key": value} or {["key"]: value}
if (property.key.type === "Literal") {
return property.key.value === name;
}
});
const matchedProperty = properties.find((property) =>
propertyNameMatches(context, property.key, property.computed, name)
);

return matchedProperty;
}
3 changes: 3 additions & 0 deletions packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from "./getChildJSXElementByName";
export * from "./getCodeModDataTag";
export * from "./getComponentImportName";
export * from "./getEndRange";
export * from "./getEnumPropertyName";
export * from "./getFromPackage";
export * from "./getImportedName";
export * from "./getImportPath";
Expand All @@ -22,12 +23,14 @@ export * from "./helpers";
export * from "./importAndExport";
export * from "./includesImport";
export * from "./interfaces";
export * from "./isEnumValue";
export * from "./isReactIcon";
export * from "./JSXAttributes";
export * from "./makeJSXElementSelfClosing";
export * from "./nodeMatches/checkMatchingImportDeclaration";
export * from "./nodeMatches/checkMatchingJSXOpeningElement";
export * from "./pfPackageMatches";
export * from "./propertyNameMatches";
export * from "./removeElement";
export * from "./removeEmptyLineAfter";
export * from "./removePropertiesFromObjectExpression";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MemberExpression } from "estree-jsx";
import { propertyNameMatches } from "./propertyNameMatches";
import { Rule } from "eslint";

/** Checks whether an enum node (MemberExpression), e.g. ButtonVariant["primary"]
* has a given enumName ("ButtonVariant") and a given propertyName ("primary"), or one of given property names. */
export function isEnumValue(
context: Rule.RuleContext,
enumExpression: MemberExpression,
enumName: string,
propertyName: string | string[]
) {
if (
enumExpression?.object?.type === "Identifier" &&
enumExpression?.object?.name === enumName
) {
const nameMatches = (name: string) =>
propertyNameMatches(
context,
enumExpression.property,
enumExpression.computed,
name
);

if (Array.isArray(propertyName)) {
return propertyName.some((name) => nameMatches(name));
}

return nameMatches(propertyName);
}

return false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Rule } from "eslint";
import { Expression, PrivateIdentifier } from "estree-jsx";
import { getVariableValue } from "./JSXAttributes";

/** Check whether a property name is of a given value.
* Property can either be of an ObjectExpression - {propName: "value"} or MemberExpression - someObject.propName */
export function propertyNameMatches(
context: Rule.RuleContext,
key: Expression | PrivateIdentifier,
computed: boolean,
name: string
) {
if (key.type === "Identifier") {
// E.g. const key = "key"; {[key]: value}; someObject[key]
if (computed) {
const scope = context.getSourceCode().getScope(key);
const propertyName = key.name;
const propertyVariableValue = getVariableValue(
propertyName,
scope,
context
).value;

return propertyVariableValue === name;
}
// E.g. {key: value}; someObject.key
return key.name === name;
}

// E.g. {"key": value} or {["key"]: value}; someObject["key"]
if (key.type === "Literal") {
return key.value === name;
}

return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = {
const attributeValue = getAttributeValue(
context,
attribute.value
);
).value;
const isValueDefault = attributeValue === "default";
const fixMessage = isValueDefault
? "remove the variant property"
Expand Down
Loading
Loading