diff --git a/.eslintrc.json b/.eslintrc.json index e60fbc1ed..bc879fb8c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,7 @@ "parserOptions": { "sourceType": "module" }, - "plugins": ["@typescript-eslint", "import"], + "plugins": ["@typescript-eslint", "import", "local-rules"], "rules": { "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": [ @@ -73,6 +73,7 @@ "import/no-internal-modules": "off", "import/order": "error", "linebreak-style": "off", + "local-rules/named-styles": "warn", "max-classes-per-file": "off", "max-len": "off", "new-parens": "off", diff --git a/eslint-local-rules.js b/eslint-local-rules.js new file mode 100644 index 000000000..30f9c51c1 --- /dev/null +++ b/eslint-local-rules.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + "named-styles": require("./lint/rules/named-styles") +}; diff --git a/lint/rules/named-styles.js b/lint/rules/named-styles.js new file mode 100644 index 000000000..eeed03bd0 --- /dev/null +++ b/lint/rules/named-styles.js @@ -0,0 +1,26 @@ +module.exports = { + create: context => ({ + CallExpression: (codePath, node) => { + if ( + ["makeStyles", "withStyles"].includes(codePath.callee.name) && + codePath.arguments.length < 2 + ) { + context.report({ + loc: codePath.callee.loc, + messageId: + codePath.callee.name === "makeStyles" + ? "expectedNameHook" + : "expectedNameHoc", + node + }); + } + } + }), + meta: { + messages: { + expectedNameHoc: 'withStyles hook should have "name" property.', + expectedNameHook: 'makeStyles hook should have "name" property.' + }, + type: "problem" + } +}; diff --git a/lint/rules/named-styles.test.js b/lint/rules/named-styles.test.js new file mode 100644 index 000000000..a956750f5 --- /dev/null +++ b/lint/rules/named-styles.test.js @@ -0,0 +1,58 @@ +const { RuleTester } = require("eslint"); + +const namedStylesRule = require("./named-styles"); + +const okCode = + 'const useStyles = makeStyles( \ + { \ + root: { \ + alignItems: "center", \ + display: "flex", \ + height: "100vh", \ + justifyContent: "center" \ + } \ + }, \ + { name: "LoginLoading" } \ +);'; + +const badCode = `const useStyles = makeStyles(theme => ({ + fileField: { + display: "none" + }, + image: { + height: "100%", + objectFit: "contain", + userSelect: "none", + width: "100%" + }, + imageContainer: { + background: "#ffffff", + border: "1px solid #eaeaea", + borderRadius: theme.spacing(), + height: 148, + justifySelf: "start", + overflow: "hidden", + padding: theme.spacing(2), + position: "relative", + width: 148 + } +}));`; + +const ruleTester = new RuleTester({ + parser: require.resolve("@typescript-eslint/parser") +}); + +ruleTester.run("named-styles", namedStylesRule, { + valid: [ + { + code: okCode + } + ], + + invalid: [ + { + code: badCode, + errors: [{ message: "makeStyles hook should be named." }] + } + ] +}); diff --git a/package-lock.json b/package-lock.json index 2ba7a96f3..69daa5c5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9871,6 +9871,12 @@ } } }, + "eslint-plugin-local-rules": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-0.1.1.tgz", + "integrity": "sha512-+Wlic7MSxhVeGJT7a8vf7thVnzlRiessyHNaaOFT7PFlQS6Ff1oMO9vD803CSI5y6Nhu/+f+bVWGUDf8SRDxvg==", + "dev": true + }, "eslint-plugin-prefer-arrow": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.1.6.tgz", diff --git a/package.json b/package.json index 7b2b0f0b0..9661bd264 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "eslint": "^6.7.1", "eslint-loader": "^3.0.2", "eslint-plugin-import": "^2.18.2", + "eslint-plugin-local-rules": "^0.1.1", "eslint-plugin-prefer-arrow": "^1.1.6", "file-loader": "^1.1.11", "fork-ts-checker-webpack-plugin": "^3.1.1",