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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { NumberInputProps } from "./NumberInput.types";
import type { StoryObj } from "@storybook/react";

export default {
title: "ADS/Components/Input/NumberInput",
title: "ADS/Components/Input/Number Input",
component: NumberInput,
decorators: [
(Story: () => React.ReactNode) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { SearchInputProps } from "./SearchInput.types";
import type { StoryObj } from "@storybook/react";

export default {
title: "ADS/Components/Input/SearchInput",
title: "ADS/Components/Input/Search Input",
component: SearchInput,
decorators: [
(Story: () => React.ReactNode) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Text } from "../../Text";
import { ListHeaderContainer } from "../EntityExplorer/styles";

const meta: Meta = {
title: "ADS/Templates/IDEHeader",
title: "ADS/Templates/IDE Header",
component: IDEHeader,
parameters: {
layout: "fullscreen",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ComboBox, ListBoxItem, Flex, Button } from "@appsmith/wds";
import { items } from "./items";

const meta: Meta<typeof ComboBox> = {
title: "WDS/Widgets/ComboBox",
title: "WDS/Widgets/Combo Box",
component: ComboBox,
tags: ["autodocs"],
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { DatePicker } from "../src";
*/
const meta: Meta<typeof DatePicker> = {
component: DatePicker,
title: "WDS/Widgets/DatePicker",
title: "WDS/Widgets/Date Picker",
};

export default meta;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { objectKeys } from "@appsmith/utils";
*/
const meta: Meta<typeof IconButton> = {
component: IconButton,
title: "WDS/Widgets/IconButton",
title: "WDS/Widgets/Icon Button",
};

export default meta;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
*/
const meta: Meta<typeof InlineButtons> = {
component: InlineButtons,
title: "WDS/Widgets/InlineButtons",
title: "WDS/Widgets/Inline Buttons",
};

export default meta;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { StoryGrid, DataAttrWrapper } from "@design-system/storybook";

const meta: Meta<typeof RadioGroup> = {
component: RadioGroup,
title: "Design System/Widgets/RadioGroup",
title: "Design System/Widgets/Radio Group",
};

export default meta;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const items = [
];

const meta: Meta<typeof RadioGroup> = {
title: "WDS/Widgets/RadioGroup",
title: "WDS/Widgets/Radio Group",
component: RadioGroup,
tags: ["autodocs"],
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { TagGroupProps } from "../src/TagGroup";
*/
const meta: Meta<typeof TagGroup> = {
component: TagGroup,
title: "WDS/Widgets/TagGroup",
title: "WDS/Widgets/Tag Group",
subcomponents: {
Tag,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import { Flex, TextArea, Button } from "@appsmith/wds";

const meta: Meta<typeof TextArea> = {
title: "WDS/Widgets/TextArea",
title: "WDS/Widgets/Text Area",
component: TextArea,
tags: ["autodocs"],
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import { Flex, Icon, TextField, Button } from "@appsmith/wds";

const meta: Meta<typeof TextField> = {
title: "WDS/Widgets/TextField",
title: "WDS/Widgets/Text Field",
component: TextField,
tags: ["autodocs"],
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TimeField } from "../src";
import { Time } from "@internationalized/date";

const meta: Meta<typeof TimeField> = {
title: "WDS/Widgets/TimeField",
title: "WDS/Widgets/Time Field",
component: TimeField,
parameters: {
docs: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const items = [
];

const meta: Meta<typeof ToggleGroup> = {
title: "WDS/Widgets/ToggleGroup",
title: "WDS/Widgets/Toggle Group",
component: ToggleGroup,
tags: ["autodocs"],
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
*/
const meta: Meta<typeof ToolbarButtons> = {
component: ToolbarButtons,
title: "WDS/Widgets/ToolbarButtons",
title: "WDS/Widgets/Toolbar Buttons",
};

export default meta;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ColorGrid } from "./ColorGrid";

const meta: Meta<typeof ColorGrid> = {
component: ColorGrid,
title: "WDS/Testing/ColorGrid",
title: "WDS/Testing/Color Grid",
args: {
source: "oklch",
size: "small",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { TSESLint } from "@typescript-eslint/utils";
import { consistentStorybookTitle } from "./rule";

const ruleTester = new TSESLint.RuleTester({
parser: require.resolve("@typescript-eslint/parser"),
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
},
});

ruleTester.run("storybook-title-case", consistentStorybookTitle, {
valid: [
{
code: `export default { title: "ADS/Templates/IDE Header" };`,
filename: "example.stories.tsx",
},
{
code: `export default { title: "ADS/Components/Input/IDE Search Input" };`,
filename: "example.stories.tsx",
},
{
code: `export default { title: "ADS/Components/Input/AAA Number Input" };`,
filename: "example.stories.tsx",
},
{
code: `const meta = { title: "WDS/Widgets/Button" };`,
filename: "example.stories.tsx",
},
],
invalid: [
{
code: `export default { title: "ADS/Templates/IDEHeader" };`,
filename: "example.stories.tsx",
errors: [
{
messageId: "invalidTitle",
data: {
title: "ADS/Templates/IDEHeader",
suggestedTitle: "ADS/Templates/IDE Header",
},
},
],
output: `export default { title: "ADS/Templates/IDE Header" };`,
},
{
code: `export default { title: "ADS/Components/Input/IDESearch Input" };`,
filename: "example.stories.tsx",
errors: [
{
messageId: "invalidTitle",
data: {
title: "ADS/Components/Input/IDESearch Input",
suggestedTitle: "ADS/Components/Input/IDE Search Input",
},
},
],
output: `export default { title: "ADS/Components/Input/IDE Search Input" };`,
},
{
code: `export default { title: "ADS/Components/Input/IDESearchInput" };`,
filename: "example.stories.tsx",
errors: [
{
messageId: "invalidTitle",
data: {
title: "ADS/Components/Input/IDESearchInput",
suggestedTitle: "ADS/Components/Input/IDE Search Input",
},
},
],
output: `export default { title: "ADS/Components/Input/IDE Search Input" };`,
},
{
code: `export default { title: "ADS/Components/Input/AAANumber Input" };`,
filename: "example.stories.tsx",
errors: [
{
messageId: "invalidTitle",
data: {
title: "ADS/Components/Input/AAANumber Input",
suggestedTitle: "ADS/Components/Input/AAA Number Input",
},
},
],
output: `export default { title: "ADS/Components/Input/AAA Number Input" };`,
},
{
code: `export default { title: "WDS/Widgets/button" };`,
filename: "example.stories.tsx",
errors: [
{
messageId: "invalidTitle",
data: {
title: "WDS/Widgets/button",
suggestedTitle: "WDS/Widgets/Button",
},
},
],
output: `export default { title: "WDS/Widgets/Button" };`,
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { TSESLint, TSESTree } from "@typescript-eslint/utils";

const titleCase = (str: string): string => {
return str
.split("/")
.map(
(segment) =>
segment
.replace(/([A-Z]+)([A-Z][a-z0-9])/g, "$1 $2") // Acronyms followed by words
.replace(/([a-z0-9])([A-Z])/g, "$1 $2") // Lowercase followed by uppercase
.replace(/\b\w/g, (char) => char.toUpperCase()), // Capitalize first letter
)
.join("/");
};

export const consistentStorybookTitle: TSESLint.RuleModule<"invalidTitle", []> =
{
defaultOptions: [],
meta: {
type: "problem",
docs: {
description:
"Ensure Storybook titles in `export default` or meta objects are in Title Case",
recommended: "error",
},
messages: {
invalidTitle:
'The Storybook title "{{ title }}" is not in Title Case. Suggested: "{{ suggestedTitle }}".',
},
schema: [], // No options
fixable: "code", // Allows auto-fixing
},

create(context) {
const filename = context.getFilename();
const isStoryFile = filename.endsWith(".stories.tsx"); // Apply only to *.stories.tsx files

if (!isStoryFile) {
return {}; // No-op for non-story files
}

const validateTitle = (title: string, node: TSESTree.Node) => {
const expectedTitle = titleCase(title);

if (title !== expectedTitle) {
context.report({
node,
messageId: "invalidTitle",
data: {
title,
suggestedTitle: expectedTitle,
},
fix: (fixer) => fixer.replaceText(node, `"${expectedTitle}"`), // Use double quotes
});
}
};

return {
ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration) {
if (
node.declaration.type === "ObjectExpression" &&
node.declaration.properties.some(
(prop) =>
prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "title" &&
prop.value.type === "Literal" &&
typeof prop.value.value === "string",
)
) {
const titleProperty = node.declaration.properties.find(
(prop) =>
prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "title",
) as TSESTree.Property;

const titleValue = titleProperty.value as TSESTree.Literal;

if (typeof titleValue.value === "string") {
validateTitle(titleValue.value, titleValue);
}
}
},

VariableDeclaration(node: TSESTree.VariableDeclaration) {
node.declarations.forEach((declaration) => {
if (
declaration.init &&
declaration.init.type === "ObjectExpression" &&
declaration.init.properties.some(
(prop) =>
prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "title" &&
prop.value.type === "Literal" &&
typeof prop.value.value === "string",
)
) {
const titleProperty = declaration.init.properties.find(
(prop) =>
prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "title",
) as TSESTree.Property;

const titleValue = titleProperty.value as TSESTree.Literal;

if (typeof titleValue.value === "string") {
validateTitle(titleValue.value, titleValue);
}
}
});
},
};
},
};
3 changes: 3 additions & 0 deletions app/client/packages/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { objectKeysRule } from "./object-keys/rule";
import { namedUseEffectRule } from "./named-use-effect/rule";
import { consistentStorybookTitle } from "./consistent-storybook-title/rule";

const plugin = {
rules: {
"object-keys": objectKeysRule,
"named-use-effect": namedUseEffectRule,
"consistent-storybook-title": consistentStorybookTitle,
},
configs: {
recommended: {
rules: {
"@appsmith/object-keys": "warn",
"@appsmith/named-use-effect": "warn",
"@appsmith/consistent-storybook-title": "error",
},
},
},
Expand Down