Skip to content

Commit 3bacbc6

Browse files
authored
fix: type getVariableValue helper (#796)
* fix: type getVariableValue helper * feat(helpers): isEnumValue and propertyNameMatches helpers * refactor(helpers) * feat(helpers): getEnumPropertyName helper * feat(helpers): update isEnumValue helper to support list of strings * feat(misc): don't use "any" type * fix(misc): get rid of type casting / explain type casting * refactor: change getAttributeValue return type * fix: remove casting
1 parent 8a46215 commit 3bacbc6

File tree

22 files changed

+474
-178
lines changed

22 files changed

+474
-178
lines changed

packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
JSXFragment,
88
JSXOpeningElement,
99
MemberExpression,
10+
Property,
11+
SpreadElement,
1012
} from "estree-jsx";
1113

1214
export function getAttribute(
@@ -35,37 +37,66 @@ export function getAnyAttribute(
3537
return foundAttribute;
3638
}
3739

40+
/**
41+
* Attribute value and its type
42+
*/
43+
type Attribute =
44+
| { type: "string"; value: string }
45+
| {
46+
type: "Literal";
47+
value: string | number | bigint | boolean | RegExp | null | undefined;
48+
}
49+
| { type: "MemberExpression"; value: MemberExpression }
50+
| { type: "ObjectExpression"; value: (Property | SpreadElement)[] }
51+
| { type: "undefined"; value: undefined };
52+
53+
const UNDEFINED: Attribute = { type: "undefined", value: undefined };
54+
55+
/**
56+
* 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.
57+
* MemberExpressions and ObjectExpressions are not parsed further.
58+
* @param context Rule context
59+
* @param node JSXAttribute value
60+
* @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"
61+
*/
3862
export function getAttributeValue(
3963
context: Rule.RuleContext,
4064
node?: JSXAttribute["value"]
41-
) {
65+
): Attribute {
4266
if (!node) {
43-
return;
67+
return UNDEFINED;
4468
}
4569

4670
const valueType = node.type;
4771

4872
if (valueType === "Literal") {
49-
return node.value;
73+
if (typeof node.value === "string") {
74+
return { type: "string", value: node.value };
75+
}
76+
return { type: "Literal", value: node.value };
5077
}
5178

5279
if (valueType !== "JSXExpressionContainer") {
53-
return;
80+
return UNDEFINED;
5481
}
5582

5683
if (node.expression.type === "Identifier") {
5784
const variableScope = context.getSourceCode().getScope(node);
5885
return getVariableValue(node.expression.name, variableScope, context);
5986
}
6087
if (node.expression.type === "MemberExpression") {
61-
return getMemberExpression(node.expression);
88+
return { type: "MemberExpression", value: node.expression };
6289
}
6390
if (node.expression.type === "Literal") {
64-
return node.expression.value;
91+
if (typeof node.expression.value === "string") {
92+
return { type: "string", value: node.expression.value };
93+
}
94+
return { type: "Literal", value: node.expression.value };
6595
}
6696
if (node.expression.type === "ObjectExpression") {
67-
return node.expression.properties;
97+
return { type: "ObjectExpression", value: node.expression.properties };
6898
}
99+
return UNDEFINED;
69100
}
70101

71102
export function getExpression(node?: JSXAttribute["value"]) {
@@ -78,15 +109,6 @@ export function getExpression(node?: JSXAttribute["value"]) {
78109
}
79110
}
80111

81-
function getMemberExpression(node: MemberExpression) {
82-
if (!node) {
83-
return;
84-
}
85-
const { object, property } = node;
86-
87-
return { object, property };
88-
}
89-
90112
export function getVariableDeclaration(
91113
name: string,
92114
scope: Scope.Scope | null
@@ -103,22 +125,39 @@ export function getVariableDeclaration(
103125
return undefined;
104126
}
105127

128+
export function getVariableInit(
129+
variableDeclaration: Scope.Variable | undefined
130+
) {
131+
if (!variableDeclaration || !variableDeclaration.defs.length) {
132+
return;
133+
}
134+
135+
const variableDefinition = variableDeclaration.defs[0];
136+
137+
if (variableDefinition.type !== "Variable") {
138+
return;
139+
}
140+
141+
return variableDefinition.node.init;
142+
}
143+
144+
/**
145+
* Helper to get the raw value of a variable, given by its name. Returns an Attribute object, similarly to getAttributeValue helper.
146+
* @param name Variable name
147+
* @param scope Scope where to look for the variable declaration
148+
* @param context Rule context
149+
* @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"
150+
*/
106151
export function getVariableValue(
107152
name: string,
108153
scope: Scope.Scope | null,
109154
context: Rule.RuleContext
110-
) {
155+
): Attribute {
111156
const variableDeclaration = getVariableDeclaration(name, scope);
112-
if (!variableDeclaration) {
113-
return;
114-
}
115-
116-
const variableInit = variableDeclaration.defs.length
117-
? variableDeclaration.defs[0].node.init
118-
: undefined;
157+
const variableInit = getVariableInit(variableDeclaration);
119158

120159
if (!variableInit) {
121-
return;
160+
return UNDEFINED;
122161
}
123162
if (variableInit.type === "Identifier") {
124163
return getVariableValue(
@@ -128,12 +167,16 @@ export function getVariableValue(
128167
);
129168
}
130169
if (variableInit.type === "Literal") {
131-
return variableInit.value;
170+
if (typeof variableInit.value === "string") {
171+
return { type: "string", value: variableInit.value };
172+
}
173+
return { type: "Literal", value: variableInit.value };
132174
}
133175
if (variableInit.type === "MemberExpression") {
134-
return getMemberExpression(variableInit);
176+
return { type: "MemberExpression", value: variableInit };
135177
}
136178
if (variableInit.type === "ObjectExpression") {
137-
return variableInit.properties;
179+
return { type: "ObjectExpression", value: variableInit.properties };
138180
}
181+
return UNDEFINED;
139182
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Rule } from "eslint";
2+
import { MemberExpression } from "estree-jsx";
3+
import { getVariableValue } from ".";
4+
5+
/** Used to get a property name on an enum (MemberExpression). */
6+
export function getEnumPropertyName(
7+
context: Rule.RuleContext,
8+
enumNode: MemberExpression
9+
) {
10+
if (enumNode.property.type === "Identifier") {
11+
// E.g. const key = "key"; someEnum[key]
12+
if (enumNode.computed) {
13+
const scope = context.getSourceCode().getScope(enumNode);
14+
const propertyName = enumNode.property.name;
15+
16+
return getVariableValue(propertyName, scope, context).value?.toString();
17+
}
18+
// E.g. someEnum.key
19+
return enumNode.property.name;
20+
}
21+
22+
// E.g. someEnum["key"]
23+
if (enumNode.property.type === "Literal") {
24+
return enumNode.property.value?.toString();
25+
}
26+
}

packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeForAttributeFixer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Rule } from "eslint";
22
import { JSXAttribute } from "estree-jsx";
3-
import { getVariableDeclaration } from "./JSXAttributes";
3+
import { getVariableDeclaration, getVariableInit } from "./JSXAttributes";
44

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

44-
return variableDeclaration && variableDeclaration.defs[0].node.init;
44+
return getVariableInit(variableDeclaration);
4545
}
4646
}
Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Rule } from "eslint";
2-
import { Property, Identifier } from "estree-jsx";
3-
import { getVariableDeclaration } from "./JSXAttributes";
2+
import { Property } from "estree-jsx";
3+
import { propertyNameMatches } from "./propertyNameMatches";
44

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

17-
const matchedProperty = properties.find((property) => {
18-
const isIdentifier = property.key.type === "Identifier";
19-
const { computed } = property;
20-
21-
// E.g. const key = "key"; {[key]: value}
22-
if (isIdentifier && computed) {
23-
const scope = context.getSourceCode().getScope(property);
24-
const propertyName = (property.key as Identifier).name;
25-
const propertyVariable = getVariableDeclaration(propertyName, scope);
26-
return propertyVariable?.defs[0].node.init.value === name;
27-
}
28-
// E.g. {key: value}
29-
if (isIdentifier && !computed) {
30-
return (property.key as Identifier).name === name;
31-
}
32-
// E.g. {"key": value} or {["key"]: value}
33-
if (property.key.type === "Literal") {
34-
return property.key.value === name;
35-
}
36-
});
17+
const matchedProperty = properties.find((property) =>
18+
propertyNameMatches(context, property.key, property.computed, name)
19+
);
3720

3821
return matchedProperty;
3922
}

packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from "./getChildJSXElementByName";
88
export * from "./getCodeModDataTag";
99
export * from "./getComponentImportName";
1010
export * from "./getEndRange";
11+
export * from "./getEnumPropertyName";
1112
export * from "./getFromPackage";
1213
export * from "./getImportedName";
1314
export * from "./getImportPath";
@@ -22,12 +23,14 @@ export * from "./helpers";
2223
export * from "./importAndExport";
2324
export * from "./includesImport";
2425
export * from "./interfaces";
26+
export * from "./isEnumValue";
2527
export * from "./isReactIcon";
2628
export * from "./JSXAttributes";
2729
export * from "./makeJSXElementSelfClosing";
2830
export * from "./nodeMatches/checkMatchingImportDeclaration";
2931
export * from "./nodeMatches/checkMatchingJSXOpeningElement";
3032
export * from "./pfPackageMatches";
33+
export * from "./propertyNameMatches";
3134
export * from "./removeElement";
3235
export * from "./removeEmptyLineAfter";
3336
export * from "./removePropertiesFromObjectExpression";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { MemberExpression } from "estree-jsx";
2+
import { propertyNameMatches } from "./propertyNameMatches";
3+
import { Rule } from "eslint";
4+
5+
/** Checks whether an enum node (MemberExpression), e.g. ButtonVariant["primary"]
6+
* has a given enumName ("ButtonVariant") and a given propertyName ("primary"), or one of given property names. */
7+
export function isEnumValue(
8+
context: Rule.RuleContext,
9+
enumExpression: MemberExpression,
10+
enumName: string,
11+
propertyName: string | string[]
12+
) {
13+
if (
14+
enumExpression?.object?.type === "Identifier" &&
15+
enumExpression?.object?.name === enumName
16+
) {
17+
const nameMatches = (name: string) =>
18+
propertyNameMatches(
19+
context,
20+
enumExpression.property,
21+
enumExpression.computed,
22+
name
23+
);
24+
25+
if (Array.isArray(propertyName)) {
26+
return propertyName.some((name) => nameMatches(name));
27+
}
28+
29+
return nameMatches(propertyName);
30+
}
31+
32+
return false;
33+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Rule } from "eslint";
2+
import { Expression, PrivateIdentifier } from "estree-jsx";
3+
import { getVariableValue } from "./JSXAttributes";
4+
5+
/** Check whether a property name is of a given value.
6+
* Property can either be of an ObjectExpression - {propName: "value"} or MemberExpression - someObject.propName */
7+
export function propertyNameMatches(
8+
context: Rule.RuleContext,
9+
key: Expression | PrivateIdentifier,
10+
computed: boolean,
11+
name: string
12+
) {
13+
if (key.type === "Identifier") {
14+
// E.g. const key = "key"; {[key]: value}; someObject[key]
15+
if (computed) {
16+
const scope = context.getSourceCode().getScope(key);
17+
const propertyName = key.name;
18+
const propertyVariableValue = getVariableValue(
19+
propertyName,
20+
scope,
21+
context
22+
).value;
23+
24+
return propertyVariableValue === name;
25+
}
26+
// E.g. {key: value}; someObject.key
27+
return key.name === name;
28+
}
29+
30+
// E.g. {"key": value} or {["key"]: value}; someObject["key"]
31+
if (key.type === "Literal") {
32+
return key.value === name;
33+
}
34+
35+
return false;
36+
}

packages/eslint-plugin-pf-codemods/src/rules/v6/bannerReplaceVariantProp/banner-replace-variantProp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module.exports = {
2727
const attributeValue = getAttributeValue(
2828
context,
2929
attribute.value
30-
);
30+
).value;
3131
const isValueDefault = attributeValue === "default";
3232
const fixMessage = isValueDefault
3333
? "remove the variant property"

0 commit comments

Comments
 (0)