Skip to content
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

Add aphrodite-add-style-variable-name lint rule #1091

Merged
merged 15 commits into from
Dec 13, 2024

Conversation

beaesguerra
Copy link
Member

@beaesguerra beaesguerra commented Dec 11, 2024

Summary:

Adding a custom lint rule to ensure consistent naming for when we use addStyle for aphrodite styling. Variable naming should be in the format StyledTag.

By having predictable names for styled elements, we are able to use component mapping more effectively for eslint-plugin-jsx-a11y so it can use the underlying HTML for accessibility checks.

Issue: FEI-5952
Related ADR: ADR-781

Test plan:

  • Tests pass
  • Linting works in the demo project (will need some help with this!)

@beaesguerra beaesguerra self-assigned this Dec 11, 2024
Copy link

changeset-bot bot commented Dec 11, 2024

🦋 Changeset detected

Latest commit: 638d66e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@khanacademy/eslint-config Major
@khanacademy/eslint-plugin Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@beaesguerra beaesguerra force-pushed the lint-rule-for-aphrodite branch from 55000ef to 770fc81 Compare December 11, 2024 00:43
@beaesguerra beaesguerra force-pushed the lint-rule-for-aphrodite branch from 770fc81 to 6811682 Compare December 11, 2024 00:55
Comment on lines +17 to +23
## Creating a new lint rule

Here are some helpful resources for setting up a new lint rule:

- [TypeScript Eslint custom rules](https://typescript-eslint.io/developers/custom-rules/)
- [AST Explorer](https://astexplorer.net/): A tool for showing what the abstract syntax tree (AST) looks like based on code
- [ESTree Spec](https://github.com/estree/estree/tree/master): The spec for learning more about the AST node types
Copy link
Member Author

@beaesguerra beaesguerra Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if there are other resources to include in this list! This is based on some resources Kevin pointed me to!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Thanks for adding these links.

@@ -0,0 +1,35 @@
# Naming convention for addStyle variable (aphrodite-add-style-variable-name)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if there are certain conventions around custom lint rules! I tried to follow similar naming and documentation structure as our other custom lint rules

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for following the existing patterns. LGTM :)

defaultOptions: [],
create(context) {
return {
VariableDeclarator(node: TSESTree.VariableDeclarator) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chatgpt helped a lot with this 😄 I verified the structure with the AST explorer as well!

data: {
variableName,
tagName,
expectedName,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to note is that it doesn't have an auto-fix option. We could potentially provide one since we have the expected name though. Auto-fixing existing places could be tricky I think since renaming a variable would mean we'd also need to update other places it is used. Let me know though if there's another way to approach this though!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably good enough for now to ship this without the autofix. addStyle(" appears 127 times across 91 files. Some of those will be invalid cases with this rule but fixing them by hand shouldn't take too long. If you want to add autofixing in the future let me know and we can pair on it.

// Placeholder example for calling addStyle for aphrodite
}

const div = addStyle("div");
Copy link
Member Author

@beaesguerra beaesguerra Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the demo project to include a linting error intentionally. Looks like it's working in CI! Some thoughts/questions:

  • I'm not sure why it shows the errors twice in the PR (same with the other errors)
  • Should CI be running linting in the demo project since we expect it to fail?
  • I followed the steps in the Demo readme locally and was running into an error. The linting error also wasn't being shown in VSCode. The error:
Oops! Something went wrong! :(

ESLint: 8.40.0

TypeError: Failed to load plugin '@khanacademy' declared in '.eslintrc.js': Class extends value undefined is not a constructor or null

@kevinb-khan Have you come across this before?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try updating eslint to 8.55.0.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circling back so I remember what we did:

  • We updated eslint versions
  • We updated the demo project to use the released eslint-plugin-khan package. We'll enable the new addStyle rule in the demo project once these changes are released
  • We ignored the demo project from lint checks so that CI can pass (it is expected that the demo project has lint errors so we can test that rules are working as expected!)

Copy link
Contributor

@kevinb-khan kevinb-khan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉 Thanks for adding this rule. I've left a few style suggestions in the comments.


The variable name when using `addStyle` should be the same as the tag argument in the format `StyledTag`.

This is useful so that Aphrodite styled elements can be mapped to HTML elements for static code analysis. For example, if the `addStyle` variables are consistently named, we are able to provide custom component mapping to `eslint-plugin-jsx-a11y` so that it can identify linting issues based on the underlying HTML tag.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a link to the docs for that rule?

Comment on lines +17 to +23
## Creating a new lint rule

Here are some helpful resources for setting up a new lint rule:

- [TypeScript Eslint custom rules](https://typescript-eslint.io/developers/custom-rules/)
- [AST Explorer](https://astexplorer.net/): A tool for showing what the abstract syntax tree (AST) looks like based on code
- [ESTree Spec](https://github.com/estree/estree/tree/master): The spec for learning more about the AST node types
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Thanks for adding these links.

Comment on lines 35 to 38
node.init &&
node.init.type === "CallExpression" &&
node.init.callee.type === "Identifier" &&
node.init.callee.name === "addStyle"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style suggestion: some of our other code compares .type fields to things like AST_NODE_TYPES.Identifier. AST_NODE_TYPES can be imported from "@typescript-eslint/utils".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style suggestion: this could be one or early returns to avoid excessive indentation, e.g.

if (node?.init.type !== "CallExpression") {
  return;
}
if (node.init.callee.type !== "Identifier" || node.init.callee.name !== "addStyle) {
  return;
}
...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback Kevin! I refactored the function to favour early returns. It is easier to read now 😄

Comment on lines 40 to 42
// Get variable name for the addStyle return value
const variableName =
node.id.type === "Identifier" ? node.id.name : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style suggestion: this check could be an early return

Comment on lines 45 to 50
const firstArg = node.init.arguments[0];
if (
firstArg &&
firstArg.type === "Literal" &&
typeof firstArg.value === "string"
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style suggestion: this check could be an early return

Comment on lines 52 to 54
const expectedName = `Styled${tagName
.charAt(0)
.toUpperCase()}${tagName.slice(1)}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: you may want to extract the part that does the capitalization into a helper function call capitalizeString. This will make it easier for people to grok what's going on here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I moved it to a toPascalCase function to handle the custom html element use case you mentioned!

data: {
variableName,
tagName,
expectedName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably good enough for now to ship this without the autofix. addStyle(" appears 127 times across 91 files. Some of those will be invalid cases with this rule but fixing them by hand shouldn't take too long. If you want to add autofixing in the future let me know and we can pair on it.

Comment on lines +49 to +51
{
code: `const StyledSup = addStyle("sup")`,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: It might be worth handling things like addStyle("foo-bar") to enable handling of custom HTML components. I know we don't use any right now, but we might someday in the future or other people/orgs might use this eslint rule (it is open source). Being pro-active is pretty low cost in this situation.

Copy link
Member Author

@beaesguerra beaesguerra Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! Instead of capitalizing the tag name for the expected variable name, I updated it to turn the string into PascalCase. I added a test for this (and other types of text casing like foo_bar or FooBar just in case!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Thank you!

Comment on lines +25 to +27
{
code: `const StyledDiv = addStyle("div")`,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this rule ignore things like const MyCompWithStyle = addStyle(MyComp)?

Copy link
Member Author

@beaesguerra beaesguerra Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, yes! It will return early if the addStyle argument is not a string:

// Return early if the argument is not a string literal
if (
firstArg?.type !== AST_NODE_TYPES.Literal ||
typeof firstArg.value !== "string"
) {
return;
}

I updated the tests and docs to explain that the naming convention only applies to string arguments for html tags!

(I didn't know addStyle could be used with components, thank you for bringing this up!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any uses of it in our code. I usually tell people to manually add a style prop. Since that's what all of our code does I guess people just copy/pasting that now which is good.

Copy link
Contributor

github-actions bot commented Dec 12, 2024

Size Change: 0 B

Total Size: 4.63 kB

ℹ️ View Unchanged
Filename Size
packages/wonder-stuff-core/dist/browser/es/index.js 1.85 kB
packages/wonder-stuff-sentry/dist/browser/es/index.js 1.65 kB
packages/wonder-stuff-testing/dist/browser/es/index.js 1.12 kB

compressed-size-action

Copy link

codecov bot commented Dec 12, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.85%. Comparing base (4cba5ba) to head (638d66e).
Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1091   +/-   ##
=======================================
  Coverage   99.85%   99.85%           
=======================================
  Files          97       97           
  Lines        1392     1392           
  Branches      358      351    -7     
=======================================
  Hits         1390     1390           
  Misses          1        1           
  Partials        1        1           

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4cba5ba...638d66e. Read the comment docs.

module.exports = {
root: true,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We set this so that the demo eslint config isn't merged with parent eslint config files!

// Placeholder example for calling addStyle for aphrodite
}

const div = addStyle("div");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circling back so I remember what we did:

  • We updated eslint versions
  • We updated the demo project to use the released eslint-plugin-khan package. We'll enable the new addStyle rule in the demo project once these changes are released
  • We ignored the demo project from lint checks so that CI can pass (it is expected that the demo project has lint errors so we can test that rules are working as expected!)

@beaesguerra beaesguerra marked this pull request as ready for review December 13, 2024 00:00
@khan-actions-bot
Copy link
Contributor

Gerald

Required Reviewers
  • @Khan/frontend-infra for changes to .eslintignore, lint_ignore.txt, package.json, yarn.lock, .changeset/brave-scissors-repair.md, .changeset/dirty-islands-bake.md, packages/eslint-config-khan/package.json, packages/eslint-plugin-khan/README.md, packages/eslint-plugin-khan/package.json, packages/eslint-plugin-khan/demo/.eslintrc.js, packages/eslint-plugin-khan/demo/package.json, packages/eslint-plugin-khan/demo/yarn.lock, packages/eslint-plugin-khan/demo/src/foo.tsx, packages/eslint-plugin-khan/src/rules/aphrodite-add-style-variable-name.ts, packages/eslint-plugin-khan/src/rules/index.ts, packages/eslint-plugin-khan/test/rules/aphrodite-add-style-variable-name.test.ts

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

@beaesguerra beaesguerra requested a review from a team December 13, 2024 00:09
Copy link
Contributor

@kevinb-khan kevinb-khan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all of the changes. This looks great. 🚀

@beaesguerra beaesguerra merged commit 3d8fea4 into main Dec 13, 2024
9 checks passed
@beaesguerra beaesguerra deleted the lint-rule-for-aphrodite branch December 13, 2024 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants