From e8b09ecd7b74a50e076c49df31d5ce6bb989d8f5 Mon Sep 17 00:00:00 2001 From: Ally <9024018+AllySummers@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:19:25 +1100 Subject: [PATCH] Add flat config preset for compiled & fix docs (#1727) * JFP-1155: Add flat config preset for compiled * JFP-1155: Make recommended property of eslint rule docs match the recommended config * JFP-1155: Fix tsconfig setup for eslint plugin for package.json file * JFP-1155: Add typescript transformer to eslint plugin for name and version * Fix typo in readme Co-authored-by: Grant Wong <2908767+dddlr@users.noreply.github.com> --------- Co-authored-by: Grant Wong <2908767+dddlr@users.noreply.github.com> --- .changeset/curvy-flies-develop.md | 5 ++ packages/eslint-plugin/README.md | 62 ++++++++++++++---- .../eslint-plugin/add-pkg-info-transform.js | 65 +++++++++++++++++++ .../src/configs/flat-recommended.ts | 16 +++++ .../eslint-plugin/src/configs/recommended.ts | 2 +- packages/eslint-plugin/src/index.ts | 31 +++++++-- .../src/rules/jsx-pragma/index.ts | 1 + .../src/rules/local-cx-xcss/index.ts | 1 + .../no-css-prop-without-css-function/index.ts | 2 +- .../index.ts | 2 + .../src/rules/no-emotion-css/index.ts | 1 + .../rules/no-empty-styled-expression/index.ts | 3 + .../src/rules/no-exported-css/index.ts | 2 + .../src/rules/no-exported-keyframes/index.ts | 2 + .../src/rules/no-invalid-css-map/index.ts | 1 + .../index.ts | 2 + .../index.ts | 2 + .../rules/shorthand-property-sorting/index.ts | 2 +- packages/eslint-plugin/tsconfig.json | 8 ++- 19 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 .changeset/curvy-flies-develop.md create mode 100644 packages/eslint-plugin/add-pkg-info-transform.js create mode 100644 packages/eslint-plugin/src/configs/flat-recommended.ts diff --git a/.changeset/curvy-flies-develop.md b/.changeset/curvy-flies-develop.md new file mode 100644 index 000000000..d9a253397 --- /dev/null +++ b/.changeset/curvy-flies-develop.md @@ -0,0 +1,5 @@ +--- +'@compiled/eslint-plugin': minor +--- + +Adding flat config preset for `@compiled/eslint-plugin` and adding missing descriptions to ESLint rules diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 42f701761..5d896ae06 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -10,13 +10,42 @@ npm install @compiled/eslint-plugin --save-dev ## Usage +### Flat Config + +Import the `@compiled/eslint-plugin` and add it to your plugins like so, then configure the rules you want to use under the "Supported rules" section. + +```ts +import compiled from '@compiled/eslint-plugin'; + +export default [ + { + plugins: { + '@compiled': compiled, + }, + rules: { + '@compiled/no-js-xcss': 'error', + }, + }, +]; +``` + +You can also enable the recommended rules for compiled by extending the `flat/recommended` config like so: + +```ts +import compiled from '@compiled/eslint-plugin'; + +export default [compiled.configs['flat/recommended']]; +``` + +### Legacy Config (`.eslintrc`) + Add `@compiled` to the plugins section of your `.eslintrc` configuration file, then configure the rules you want to use under the rules section. ```json { "plugins": ["@compiled"], "rules": { - "@compiled/rule-name": "error" + "@compiled/no-js-xcss": "error" } } ``` @@ -32,15 +61,22 @@ You can also enable the recommended rules for compiled by adding `plugin:@compil ## Supported rules -:white_check_mark: = recommended, :wrench: = automatically fixable, :bulb: = manually fixable - -| Name | Description | :white_check_mark: | Fixable | -| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | :----------------: | :------: | -| [@compiled/jsx-pragma](./src/rules/jsx-pragma) | Enforces a jsx pragma when using the `css` prop | | :wrench: | -| [@compiled/no-css-tagged-template-expression](./src/rules/no-css-tagged-template-expression) | Disallows the `css` tagged template expression | :white_check_mark: | :wrench: | -| [@compiled/no-emotion-css](./src/rules/no-emotion-css) | Disallows `@emotion` usages | | :wrench: | -| [@compiled/no-exported-css](./src/rules/no-exported-css) | Disallows `css` usages from being exported | :white_check_mark: | | -| [@compiled/no-exported-keyframes](./src/rules/no-exported-keyframes) | Disallows `keyframes` usages from being exported | :white_check_mark: | | -| [@compiled/no-keyframes-tagged-template-expression](./src/rules/no-keyframes-tagged-template-expression) | Disallows the `keyframes` tagged template expression | :white_check_mark: | :wrench: | -| [@compiled/no-styled-tagged-template-expression](./src/rules/no-styled-tagged-template-expression) | Disallows the `styled` tagged template expression | :white_check_mark: | :wrench: | -| [@compiled/no-css-prop-without-css-function](./src/rules/no-css-prop-without-css-function) | Disallows css prop without the css function | :white_check_mark: | :wrench: | +✅ Included in the recommended configuration.\ +🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ + +| Name | Description | Recommended | Fixable | +| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------: | :-----: | +| [@compiled/jsx-pragma](./src/rules/jsx-pragma) | Enforces a jsx pragma when using the `css` prop | | 🔧 | +| [@compiled/local-cx-xcss](./src/rules/local-cx-xcss) | Ensures the `cx()` function is only used within the `xcss` prop | ✅ | | +| [@compiled/no-css-prop-without-css-function](./src/rules/no-css-prop-without-css-function) | Disallows `css` prop usages where it is either not wrapped in the `css` import from `@compiled/react` or where `@compiled` cannot determine whether the `css` import is included at build time. | ✅ | 🔧 | +| [@compiled/no-css-tagged-template-expression](./src/rules/no-css-tagged-template-expression) | Disallows the `css` tagged template expression | ✅ | 🔧 | +| [@compiled/no-emotion-css](./src/rules/no-emotion-css) | Disallows `@emotion` usages | | 🔧 | +| [@compiled/no-empty-styled-expression](./src/rules/no-empty-styled-expression) | Disallows any `styled` expression to be used when passing empty arguments in `@compiled/react` | ✅ | | +| [@compiled/no-exported-css](./src/rules/no-exported-css) | Disallows `css` usages from being exported | ✅ | | +| [@compiled/no-exported-keyframes](./src/rules/no-exported-keyframes) | Disallows `keyframes` usages from being exported | ✅ | | +| [@compiled/no-invalid-css-map](./src/rules/no-invalid-css-map) | Checks the validity of a CSS map created through cssMap. This is intended to be used alongside TypeScript's type-checking. | ✅ | | +| [@compiled/no-js-xcss](./src/rules/no-js-xcss) | The xcss prop is predicated on adhering to the type contract. Using it without TypeScript breaks this contract and thus is not allowed. | ✅ | | +| [@compiled/no-keyframes-tagged-template-expression](./src/rules/no-keyframes-tagged-template-expression) | Disallows the `keyframes` tagged template expression | ✅ | 🔧 | +| [@compiled/no-styled-tagged-template-expression](./src/rules/no-styled-tagged-template-expression) | Disallows the `styled` tagged template expression | ✅ | 🔧 | +| [@compiled/no-suppress-xcss](./src/rules/no-suppress-xcss) | The xcss prop is predicated on adhering to the type contract. Supressing it breaks this contract and thus is not allowed. | ✅ | | +| [@compiled/shorthand-property-sorting](./src/rules/shorthand-property-sorting) | Prevent unwanted side-effect by ensuring shorthand properties are always defined before their related longhands. | ✅ | | diff --git a/packages/eslint-plugin/add-pkg-info-transform.js b/packages/eslint-plugin/add-pkg-info-transform.js new file mode 100644 index 000000000..98c91e6ce --- /dev/null +++ b/packages/eslint-plugin/add-pkg-info-transform.js @@ -0,0 +1,65 @@ +/** + * @fileoverview + * This file is a transformer to be used with `ttypescript` to replace the version + * and name of the eslint plugin in the source from the package.json at compile time. + */ + +// @ts-check +// eslint-disable-next-line import/no-extraneous-dependencies +const ts = require('typescript'); + +const pkgJson = require('./package.json'); + +/** + * + * @param {ts.Node} node + * @param {string} name + * @param {string} value + * @returns {node is ts.VariableDeclaration & {name: ts.Identifier, initializer: ts.StringLiteral}} + */ +const isVariableWithProperties = (node, name, value) => + !!( + ts.isVariableDeclaration(node) && + ts.isIdentifier(node.name) && + node.name.text === name && + node.initializer && + ts.isStringLiteral(node.initializer) && + node.initializer.text === value + ); + +/** + * @param {ts.Program} _ts + * @returns {ts.TransformerFactory} + */ +const transformer = (_ts) => (context) => (sourceFile) => { + /** + * @param {ts.Node} node + * @returns {ts.Node} + */ + const visitor = (node) => { + if (isVariableWithProperties(node, 'name', '/* NAME */')) { + return ts.factory.updateVariableDeclaration( + node, + node.name, + undefined, + undefined, + ts.factory.createStringLiteral(pkgJson.name) + ); + } + + if (isVariableWithProperties(node, 'version', '/* VERSION */')) { + return ts.factory.updateVariableDeclaration( + node, + node.name, + undefined, + undefined, + ts.factory.createStringLiteral(pkgJson.version) + ); + } + + return ts.visitEachChild(node, visitor, context); + }; + return ts.visitNode(sourceFile, visitor); +}; + +module.exports = transformer; diff --git a/packages/eslint-plugin/src/configs/flat-recommended.ts b/packages/eslint-plugin/src/configs/flat-recommended.ts new file mode 100644 index 000000000..2609f2151 --- /dev/null +++ b/packages/eslint-plugin/src/configs/flat-recommended.ts @@ -0,0 +1,16 @@ +export const flatRecommended = { + // plugin is not specified here because flat config needs a reference to the plugin + rules: { + '@compiled/local-cx-xcss': 'error', + '@compiled/no-css-prop-without-css-function': 'error', + '@compiled/no-css-tagged-template-expression': 'error', + '@compiled/no-empty-styled-expression': 'error', + '@compiled/no-exported-css': 'error', + '@compiled/no-exported-keyframes': 'error', + '@compiled/no-invalid-css-map': 'error', + '@compiled/no-js-xcss': 'error', + '@compiled/no-keyframes-tagged-template-expression': 'error', + '@compiled/no-styled-tagged-template-expression': 'error', + '@compiled/no-suppress-xcss': 'error', + }, +} as const; diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index ffa6b29ce..9a7e65d80 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -4,6 +4,7 @@ export const recommended = { '@compiled/local-cx-xcss': 'error', '@compiled/no-css-prop-without-css-function': 'error', '@compiled/no-css-tagged-template-expression': 'error', + '@compiled/no-empty-styled-expression': 'error', '@compiled/no-exported-css': 'error', '@compiled/no-exported-keyframes': 'error', '@compiled/no-invalid-css-map': 'error', @@ -11,6 +12,5 @@ export const recommended = { '@compiled/no-keyframes-tagged-template-expression': 'error', '@compiled/no-styled-tagged-template-expression': 'error', '@compiled/no-suppress-xcss': 'error', - '@compiled/no-empty-styled-expression': 'error', }, }; diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 61c2a18a2..32765bb02 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -1,3 +1,4 @@ +import { flatRecommended } from './configs/flat-recommended'; import { recommended } from './configs/recommended'; import { jsxPragmaRule } from './rules/jsx-pragma'; import { localCXXCSSRule } from './rules/local-cx-xcss'; @@ -14,6 +15,9 @@ import { noStyledTaggedTemplateExpressionRule } from './rules/no-styled-tagged-t import { noSuppressXCSS } from './rules/no-suppress-xcss'; import { shorthandFirst } from './rules/shorthand-property-sorting'; +export const name = '/* NAME */'; +export const version = '/* VERSION */'; + export const rules = { 'jsx-pragma': jsxPragmaRule, 'local-cx-xcss': localCXXCSSRule, @@ -29,8 +33,27 @@ export const rules = { 'no-suppress-xcss': noSuppressXCSS, 'no-empty-styled-expression': noEmptyStyledExpressionRule, 'shorthand-property-sorting': shorthandFirst, -}; +} as const; + +export const plugin = { + meta: { + name, + version, + }, + rules, + configs: { + recommended, + 'flat/recommended': { + ...flatRecommended, + plugins: { + get '@compiled'() { + return plugin; + }, + }, + }, + }, +} as const; + +export const configs = plugin.configs; -export const configs = { - recommended, -}; +export default plugin; diff --git a/packages/eslint-plugin/src/rules/jsx-pragma/index.ts b/packages/eslint-plugin/src/rules/jsx-pragma/index.ts index 9df69b7d4..8049c0f11 100644 --- a/packages/eslint-plugin/src/rules/jsx-pragma/index.ts +++ b/packages/eslint-plugin/src/rules/jsx-pragma/index.ts @@ -114,6 +114,7 @@ function createFixer(context: Rule.RuleContext, source: SourceCode, options: Opt export const jsxPragmaRule: Rule.RuleModule = { meta: { docs: { + description: 'Enforces a jsx pragma when using the `css` prop', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/jsx-pragma', }, fixable: 'code', diff --git a/packages/eslint-plugin/src/rules/local-cx-xcss/index.ts b/packages/eslint-plugin/src/rules/local-cx-xcss/index.ts index be4df07e0..bcd31336f 100644 --- a/packages/eslint-plugin/src/rules/local-cx-xcss/index.ts +++ b/packages/eslint-plugin/src/rules/local-cx-xcss/index.ts @@ -19,6 +19,7 @@ function getParentJSXAttribute(node: Rule.Node) { export const localCXXCSSRule: Rule.RuleModule = { meta: { docs: { + description: 'Ensures the `cx()` function is only used within the `xcss` prop', recommended: true, url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/local-cx-xcss', }, diff --git a/packages/eslint-plugin/src/rules/no-css-prop-without-css-function/index.ts b/packages/eslint-plugin/src/rules/no-css-prop-without-css-function/index.ts index d8ae4bc86..fc9ccd17d 100644 --- a/packages/eslint-plugin/src/rules/no-css-prop-without-css-function/index.ts +++ b/packages/eslint-plugin/src/rules/no-css-prop-without-css-function/index.ts @@ -222,7 +222,7 @@ export const noCssPropWithoutCssFunctionRule: TSESLint.RuleModule = { url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-css-prop-without-css-function', recommended: 'error', description: - 'Disallows `css` prop usages without wrapping in the `css` import from `@compiled/react`. Also forbids `css` prop usages where Compiled cannot determine whether the `css` import is included at build time.', + 'Disallows `css` prop usages where it is either not wrapped in the `css` import from `@compiled/react` or where `@compiled` cannot determine whether the `css` import is included at build time.', }, messages: { noCssFunction: 'css prop values are required to use the css import from @compiled/react', diff --git a/packages/eslint-plugin/src/rules/no-css-tagged-template-expression/index.ts b/packages/eslint-plugin/src/rules/no-css-tagged-template-expression/index.ts index 742f9570c..9688c2fce 100644 --- a/packages/eslint-plugin/src/rules/no-css-tagged-template-expression/index.ts +++ b/packages/eslint-plugin/src/rules/no-css-tagged-template-expression/index.ts @@ -5,6 +5,8 @@ import { createNoTaggedTemplateExpressionRule, isCss } from '../../utils'; export const noCssTaggedTemplateExpressionRule: Rule.RuleModule = { meta: { docs: { + recommended: true, + description: 'Disallows the `css` tagged template expression', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-css-tagged-template-expression', }, fixable: 'code', diff --git a/packages/eslint-plugin/src/rules/no-emotion-css/index.ts b/packages/eslint-plugin/src/rules/no-emotion-css/index.ts index d4e68e5d8..b678b3bce 100644 --- a/packages/eslint-plugin/src/rules/no-emotion-css/index.ts +++ b/packages/eslint-plugin/src/rules/no-emotion-css/index.ts @@ -29,6 +29,7 @@ export const noEmotionCssRule: Rule.RuleModule = { fixable: 'code', type: 'problem', docs: { + description: 'Disallows `@emotion` usages', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-emotion-css', }, messages: { diff --git a/packages/eslint-plugin/src/rules/no-empty-styled-expression/index.ts b/packages/eslint-plugin/src/rules/no-empty-styled-expression/index.ts index c2ef5f89c..ef624d4c4 100644 --- a/packages/eslint-plugin/src/rules/no-empty-styled-expression/index.ts +++ b/packages/eslint-plugin/src/rules/no-empty-styled-expression/index.ts @@ -50,6 +50,9 @@ const createNoEmptyStyledExpressionRule = export const noEmptyStyledExpressionRule: Rule.RuleModule = { meta: { docs: { + recommended: true, + description: + 'Disallows any `styled` expression to be used when passing empty arguments in `@compiled/react`', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-empty-styled-expression', }, messages: { diff --git a/packages/eslint-plugin/src/rules/no-exported-css/index.ts b/packages/eslint-plugin/src/rules/no-exported-css/index.ts index 1eedebae2..1230eaa95 100644 --- a/packages/eslint-plugin/src/rules/no-exported-css/index.ts +++ b/packages/eslint-plugin/src/rules/no-exported-css/index.ts @@ -5,6 +5,8 @@ import { createNoExportedRule, isCss } from '../../utils'; export const noExportedCssRule: Rule.RuleModule = { meta: { docs: { + recommended: true, + description: 'Disallows `css` usages from being exported', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-exported-css', }, messages: { diff --git a/packages/eslint-plugin/src/rules/no-exported-keyframes/index.ts b/packages/eslint-plugin/src/rules/no-exported-keyframes/index.ts index a31edfd47..93dde6eef 100644 --- a/packages/eslint-plugin/src/rules/no-exported-keyframes/index.ts +++ b/packages/eslint-plugin/src/rules/no-exported-keyframes/index.ts @@ -5,6 +5,8 @@ import { createNoExportedRule, isKeyframes } from '../../utils'; export const noExportedKeyframesRule: Rule.RuleModule = { meta: { docs: { + recommended: true, + description: 'Disallows `keyframes` usages from being exported', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-exported-css', }, messages: { diff --git a/packages/eslint-plugin/src/rules/no-invalid-css-map/index.ts b/packages/eslint-plugin/src/rules/no-invalid-css-map/index.ts index ff76ad219..d7eb4d21c 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-css-map/index.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-css-map/index.ts @@ -69,6 +69,7 @@ const createCssMapRule = (context: Rule.RuleContext): Rule.RuleListener => { export const noInvalidCssMapRule: Rule.RuleModule = { meta: { docs: { + recommended: true, description: "Checks the validity of a CSS map created through cssMap. This is intended to be used alongside TypeScript's type-checking.", url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-invalid-css-map', diff --git a/packages/eslint-plugin/src/rules/no-keyframes-tagged-template-expression/index.ts b/packages/eslint-plugin/src/rules/no-keyframes-tagged-template-expression/index.ts index afb86177a..1f64020aa 100644 --- a/packages/eslint-plugin/src/rules/no-keyframes-tagged-template-expression/index.ts +++ b/packages/eslint-plugin/src/rules/no-keyframes-tagged-template-expression/index.ts @@ -5,6 +5,8 @@ import { createNoTaggedTemplateExpressionRule, isKeyframes } from '../../utils'; export const noKeyframesTaggedTemplateExpressionRule: Rule.RuleModule = { meta: { docs: { + recommended: true, + description: 'Disallows the `keyframes` tagged template expression', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-keyframes-tagged-template-expression', }, fixable: 'code', diff --git a/packages/eslint-plugin/src/rules/no-styled-tagged-template-expression/index.ts b/packages/eslint-plugin/src/rules/no-styled-tagged-template-expression/index.ts index 6f0aa74e8..c6e314052 100644 --- a/packages/eslint-plugin/src/rules/no-styled-tagged-template-expression/index.ts +++ b/packages/eslint-plugin/src/rules/no-styled-tagged-template-expression/index.ts @@ -5,6 +5,8 @@ import { createNoTaggedTemplateExpressionRule, isStyled } from '../../utils'; export const noStyledTaggedTemplateExpressionRule: Rule.RuleModule = { meta: { docs: { + recommended: true, + description: 'Disallows the `styled` tagged template expression', url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-styled-tagged-template-expression', }, fixable: 'code', diff --git a/packages/eslint-plugin/src/rules/shorthand-property-sorting/index.ts b/packages/eslint-plugin/src/rules/shorthand-property-sorting/index.ts index c7f9c126f..2901f4822 100644 --- a/packages/eslint-plugin/src/rules/shorthand-property-sorting/index.ts +++ b/packages/eslint-plugin/src/rules/shorthand-property-sorting/index.ts @@ -57,7 +57,7 @@ export const shorthandFirst: Rule.RuleModule = { meta: { docs: { description: - 'Prevent unwanted side-effect by ensuring shorthand properties are always defined before their related longhands. See more in the README.', + 'Prevent unwanted side-effect by ensuring shorthand properties are always defined before their related longhands.', recommended: true, url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/shorthand-property-sorting', }, diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index 41c69bd0c..094b37a8e 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -4,7 +4,13 @@ "rootDir": "src", "outDir": "dist", "module": "commonjs", - "target": "es2017" + "target": "es2017", + "plugins": [ + { + "name": "transform-pkg-info", + "transform": "./add-pkg-info-transform.js" + } + ] }, "references": [{ "path": "../jest" }, { "path": "../utils" }] }