Skip to content

Commit

Permalink
set up aphrodite add style lint rule
Browse files Browse the repository at this point in the history
  • Loading branch information
beaesguerra committed Dec 11, 2024
1 parent 717bfea commit ddb29e9
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin-khan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ eslint plugin with our set of custom rules for various things
- [khan/react-no-subscriptions-before-mount](docs/react-no-subscriptions-before-mount.md)
- [khan/react-svg-path-precision](docs/react-svg-path-precision.md)
- [khan/sync-tag](docs/sync-tag.md)
- [khan/aphrodite-add-style-variable-name](docs/aphrodite-add-style-variable-name.md)

## Creating a new lint rule

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Naming convention for addStyle variable (aphrodite-add-style-variable-name)

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.

## Rule Details

The following are considered warnings:

```ts
const div = addStyle("div");
```

```ts
const foo = addStyle("span");
```

```ts
const container = addStyle("div");
```

The following are not considered warnings:

```ts
const styledDiv = addStyle("div");
```

```ts
const styledSpan = addStyle("span");
```

```ts
const styledImg = addStyle("img");
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {TSESTree, ESLintUtils} from "@typescript-eslint/utils";

import type {MyPluginDocs} from "../types";

const createRule = ESLintUtils.RuleCreator<MyPluginDocs>(
(name) =>
`https://github.com/Khan/wonder-stuff/blob/main/packages/eslint-plugin-khan/docs/${name}.md`,
);

type Options = [];
type MessageIds = "errorString";

const message = `Variable name "{{ variableName }}" does not match tag name "{{ tagName }}". Variable name should be "{{ expectedName }}"`;

export default createRule<Options, MessageIds>({
name: "aphrodite-add-style-variable-name",
meta: {
docs: {
description:
"Ensure variable names match the tag name passed to addStyle and follow the format: styledTag (ie. styledDiv, styledImg)",
recommended: true,
},
messages: {
errorString: message,
},
schema: [],
type: "problem",
},
defaultOptions: [],
create(context) {
return {
VariableDeclarator(node: TSESTree.VariableDeclarator) {
// Check if addStyle is being called
if (
node.init &&
node.init.type === "CallExpression" &&
node.init.callee.type === "Identifier" &&
node.init.callee.name === "addStyle"
) {
// Get variable name for the addStyle return value
const variableName =
node.id.type === "Identifier" ? node.id.name : null;

// Get the tag name that was passed into addStyle
const firstArg = node.init.arguments[0];
if (
firstArg &&
firstArg.type === "Literal" &&
typeof firstArg.value === "string"
) {
const tagName = firstArg.value;
const expectedName = `styled${tagName
.charAt(0)
.toUpperCase()}${tagName.slice(1)}`;

// Check if the variable name matches the expected pattern
if (variableName !== expectedName) {
context.report({
node: node.id,
messageId: "errorString",
data: {
variableName,
tagName,
expectedName,
},
});
}
}
}
},
};
},
});
2 changes: 2 additions & 0 deletions packages/eslint-plugin-khan/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import reactSvgPathPrecision from "./react-svg-path-precision";
import syncTag from "./sync-tag";
import tsNoErrorSupressions from "./ts-no-error-suppressions";
import jestEnzymeMatchers from "./jest-enzyme-matchers";
import aphroditeAddStyleVariableName from "./aphrodite-add-style-variable-name";

export default {
"array-type-style": arrayTypeStyle,
Expand All @@ -20,4 +21,5 @@ export default {
"react-svg-path-precision": reactSvgPathPrecision,
"sync-tag": syncTag,
"jest-enzyme-matchers": jestEnzymeMatchers,
"aphrodite-add-style-variable-name": aphroditeAddStyleVariableName,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {RuleTester} from "@typescript-eslint/rule-tester";

import {rules} from "../../src/index";

const ruleTester = new RuleTester({
languageOptions: {
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
ecmaFeatures: {},
},
},
linterOptions: {
// NOTE(kevinb): Avoids 'TypeError: Expected a Boolean' error
// when running the tests.
reportUnusedDisableDirectives: true,
},
});

const ruleName = "aphrodite-add-style-variable-name";
const rule = rules[ruleName];

ruleTester.run(ruleName, rule, {
valid: [
{
code: `const styledDiv = addStyle("div")`,
},
{
code: `const styledSpan = addStyle("span")`,
},
{
code: `const styledImg = addStyle("img")`,
},
{
code: `const styledUl = addStyle("ul")`,
},
{
code: `const styledOl = addStyle("ol")`,
},
{
code: `const styledLi = addStyle("li")`,
},
{
code: `const styledButton = addStyle("button")`,
},
{
code: `const styledP = addStyle("p")`,
},
{
code: `const styledSup = addStyle("sup")`,
},
],
invalid: [
{
code: `const foo = addStyle("div")`,
errors: [
{
messageId: "errorString",
data: {
variableName: "foo",
tagName: "div",
expectedName: "styledDiv",
},
},
],
},
{
code: `const div = addStyle("div")`,
errors: [
{
messageId: "errorString",
data: {
variableName: "div",
tagName: "div",
expectedName: "styledDiv",
},
},
],
},
{
code: `const span = addStyle("span")`,
errors: [
{
messageId: "errorString",
data: {
variableName: "span",
tagName: "span",
expectedName: "styledSpan",
},
},
],
},
{
code: `const p = addStyle("p")`,
errors: [
{
messageId: "errorString",
data: {
variableName: "p",
tagName: "p",
expectedName: "styledP",
},
},
],
},
],
});

0 comments on commit ddb29e9

Please sign in to comment.