Skip to content

Commit

Permalink
Misc: ESLint plugin testing (BuilderIO#456)
Browse files Browse the repository at this point in the history
* fix: root eslint config

* examples: add eslint plugin to example

* docs(eslint): document recommended config

* chore: remove commented out code

* fix buggy name

* chore(eslint): move rule into file

* fix(eslint): strongly type recommended rules to match rules

* fix(tests): move test and fix import path

* publish eslint plugin

* publish eslint plugin
  • Loading branch information
samijaber authored Jun 8, 2022
1 parent a8de461 commit d4f815f
Show file tree
Hide file tree
Showing 14 changed files with 444 additions and 144 deletions.
5 changes: 0 additions & 5 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ module.exports = {
browser: true,
jest: true,
},
plugins: ['@builder.io/mitosis'],
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
extends: [],
parserOptions: {
Expand All @@ -14,8 +13,4 @@ module.exports = {
jsx: true, // Allows for the parsing of JSX
},
},

rules: {
// '@builder.io/mitosis/no-conditional-render': 'warn',
},
};
3 changes: 3 additions & 0 deletions .yarn/versions/46f8ffda.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
undecided:
- "@builder.io/mitosis-repo"
- "@builder.io/basic-example"
2 changes: 2 additions & 0 deletions examples/basic/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
output/
20 changes: 20 additions & 0 deletions examples/basic/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@builder.io/mitosis/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['@typescript-eslint', '@builder.io/mitosis'],
};
7 changes: 5 additions & 2 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
"seedrandom": "^3.0.5"
},
"devDependencies": {
"@builder.io/eslint-plugin-mitosis": "workspace:*",
"@types/jest": "^27.4.0",
"@types/node-fetch": "^2.5.12",
"eslint": "^7.21.0",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"eslint": "^8.17.0",
"jest": "^27.5.1",
"jest-fetch-mock": "^3.0.3",
"prettier": "^2.5.0",
"ts-jest": "^27.1.3",
"typescript": "^4.5.5",
"typescript": "^4.7.3",
"watch": "^1.0.2"
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
},
"devDependencies": {
"@babel/preset-env": "^7.6.0",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"commitizen": "^3.0.2",
"esbuild": "0.14.25",
"esbuild-register": "3.3.2",
Expand Down
10 changes: 5 additions & 5 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ Finally, add the plugin to the `plugins` array, and the rules you want to the `r

```js
module.exports = {
// [...other settings]
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
plugins: [
// [...other plugins]
'@builder.io/mitosis',
plugins: ['@builder.io/mitosis'],
extends: [
// Use this approach for our recommended rules configuration
'plugin:@builder.io/mitosis/recommended',
],
rules: {
// example of adding one of our rules
// Use this to configure rules individually
'@builder.io/mitosis/css-no-vars': 'error',
},
};
Expand Down
5 changes: 2 additions & 3 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@builder.io/eslint-plugin-mitosis",
"version": "0.0.1-1",
"version": "0.0.1",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "MIT",
Expand Down Expand Up @@ -43,6 +43,5 @@
"dependencies": {
"@babel/types": "7.12.6",
"ts-pattern": "^3.3.5"
},
"stableVersion": "0.0.1-0"
}
}
43 changes: 26 additions & 17 deletions packages/eslint-plugin/src/configs/recommended.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import { rules } from '../rules';

const PLUGIN_NAME = '@builder.io/mitosis' as const;

type RulesKeys = `${typeof PLUGIN_NAME}/${keyof typeof rules}`;

const recommendedRules: Record<RulesKeys, string> = {
'@builder.io/mitosis/css-no-vars': 'error',
'@builder.io/mitosis/jsx-callback-arg-name': 'error',
'@builder.io/mitosis/jsx-callback-arrow-function': 'error',
'@builder.io/mitosis/no-assign-props-to-state': 'error',
'@builder.io/mitosis/no-async-methods-on-state': 'error',
'@builder.io/mitosis/no-conditional-logic-in-component-render': 'error',
'@builder.io/mitosis/no-state-destructuring': 'error',
'@builder.io/mitosis/no-var-declaration-in-jsx': 'error',
'@builder.io/mitosis/no-var-declaration-or-assignment-in-component': 'error',
'@builder.io/mitosis/no-var-name-same-as-state-property': 'error',
'@builder.io/mitosis/only-default-function-and-imports': 'error',
'@builder.io/mitosis/ref-no-current': 'error',
'@builder.io/mitosis/use-state-var-declarator': 'error',
'@builder.io/mitosis/static-control-flow': 'error',
'@builder.io/mitosis/no-var-name-same-as-prop-name': 'error',
};

export default {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
plugins: ['@builder.io/mitosis'],
rules: {
'@builder.io/mitosis/css-no-vars': 'error',
'@builder.io/mitosis/jsx-callback-arg-name': 'error',
'@builder.io/mitosis/jsx-callback-arrow-function': 'error',
'@builder.io/mitosis/no-assign-props-to-state': 'error',
'@builder.io/mitosis/no-async-methods-on-state': 'error',
'@builder.io/mitosis/no-conditional-logic-in-component-render': 'error',
'@builder.io/mitosis/no-state-destructuring': 'error',
'@builder.io/mitosis/no-var-declaration-in-jsx': 'error',
'@builder.io/mitosis/no-var-declaration-or-assignment-in-component':
'error',
'@builder.io/mitosis/no-var-name-same-as-state-property': 'error',
'@builder.io/mitosis/only-default-function-and-imports': 'error',
'@builder.io/mitosis/ref-no-current': 'error',
'@builder.io/mitosis/use-state-var-declarator': 'error',
},
plugins: [PLUGIN_NAME],
rules: recommendedRules,
};
102 changes: 1 addition & 101 deletions packages/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,6 @@
import { Rule } from 'eslint';
import * as types from '@babel/types';
import isMitosisPath from './helpers/isMitosisPath';
import cssNoVars from './rules/css-no-vars';
import refNoCurrent from './rules/ref-no-current';
import noStateDestructuring from './rules/no-state-destructuring';
import jsxCallbackArgNameRule from './rules/jsx-callback-arg-name';
import noAssignPropsToState from './rules/no-assign-props-to-state';
import useStateVarDeclarator from './rules/use-state-var-declarator';
import noAsyncMethodsOnState from './rules/no-async-methods-on-state';
import notVarDeclarationInJSX from './rules/no-var-declaration-in-jsx';
import jsxCallbackArrowFunction from './rules/jsx-callback-arrow-function';
import noVarNameSameAsPropName from './rules/no-var-name-same-as-prop-name';
import noVarNameSameAsStateProperty from './rules/no-var-name-same-as-state-property';
import onlyDefaultFunctionAndImports from './rules/only-default-function-and-imports';
import noConditionalLogicInComponentRender from './rules/no-conditional-logic-in-component-render';
import noVarDeclarationOrAssignmentInComponent from './rules/no-var-declaration-or-assignment-in-component';
import recommended from './configs/recommended';
export const staticControlFlow: Rule.RuleModule = {
create(context) {
if (!isMitosisPath(context.getFilename())) return {};

return {
VariableDeclarator(node) {
if (types.isVariableDeclarator(node)) {
if (
types.isObjectPattern(node.id) &&
types.isIdentifier(node.init) &&
node.init.name === 'state'
) {
context.report({
node: node as any,
message:
'Destructuring the state object is currently not supported',
});
}
}
},

CallExpression(node) {
if (types.isCallExpression(node)) {
if (
types.isIdentifier(node.callee) &&
node.callee.name === 'useEffect'
) {
const useEffectMessage =
'Only useEffect with an empty array second argument is allowed. E.g. useEffect(...) must be useEffect(..., [])';
const secondArg = node.arguments[1];
if (
!(
secondArg &&
types.isArrayExpression(secondArg) &&
secondArg.elements.length === 0
)
) {
context.report({
node: node,
message: useEffectMessage,
});
}
}
}
},

JSXExpressionContainer(node) {
if (types.isJSXExpressionContainer(node)) {
if (types.isConditionalExpression(node.expression)) {
if (
types.isJSXElement(node.expression.consequent) ||
types.isJSXElement(node.expression.alternate)
) {
context.report({
node: node as any,
message:
'Ternaries around JSX Elements are not currently supported. Instead use binary expressions - e.g. {foo ? <bar /> : <baz />} should be {foo && <bar />}{!foo && <baz />}',
});
}
}
}
},
};
},
};

export const rules = {
'css-no-vars': cssNoVars,
'res-no-current': refNoCurrent,
'static-control-flow': staticControlFlow,
'no-state-destructuring': noStateDestructuring,
'jsx-callback-arg-name': jsxCallbackArgNameRule,
'no-assign-props-to-state': noAssignPropsToState,
'use-state-var-declarator': useStateVarDeclarator,
'no-async-methods-on-state': noAsyncMethodsOnState,
'no-var-declaration-in-jsx': notVarDeclarationInJSX,
'no-var-name-same-as-prop-name': noVarNameSameAsPropName,
'jsx-callback-arrow-function': jsxCallbackArrowFunction,
'no-var-name-same-as-state-property': noVarNameSameAsStateProperty,
'only-default-function-and-imports': onlyDefaultFunctionAndImports,
'no-conditional-logic-in-component-render':
noConditionalLogicInComponentRender,
'no-var-declaration-or-assignment-in-component':
noVarDeclarationOrAssignmentInComponent,
};
export { rules } from './rules';

export const configs = {
recommended,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RuleTester } from 'eslint';

import { staticControlFlow } from '../index';
import { staticControlFlow } from '../static-control-flow';

const ruleTester = new RuleTester();

Expand Down
35 changes: 35 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import cssNoVars from './css-no-vars';
import refNoCurrent from './ref-no-current';
import noStateDestructuring from './no-state-destructuring';
import jsxCallbackArgNameRule from './jsx-callback-arg-name';
import noAssignPropsToState from './no-assign-props-to-state';
import useStateVarDeclarator from './use-state-var-declarator';
import noAsyncMethodsOnState from './no-async-methods-on-state';
import notVarDeclarationInJSX from './no-var-declaration-in-jsx';
import jsxCallbackArrowFunction from './jsx-callback-arrow-function';
import noVarNameSameAsPropName from './no-var-name-same-as-prop-name';
import noVarNameSameAsStateProperty from './no-var-name-same-as-state-property';
import onlyDefaultFunctionAndImports from './only-default-function-and-imports';
import noConditionalLogicInComponentRender from './no-conditional-logic-in-component-render';
import noVarDeclarationOrAssignmentInComponent from './no-var-declaration-or-assignment-in-component';
import { staticControlFlow } from './static-control-flow';

export const rules = {
'css-no-vars': cssNoVars,
'ref-no-current': refNoCurrent,
'static-control-flow': staticControlFlow,
'no-state-destructuring': noStateDestructuring,
'jsx-callback-arg-name': jsxCallbackArgNameRule,
'no-assign-props-to-state': noAssignPropsToState,
'use-state-var-declarator': useStateVarDeclarator,
'no-async-methods-on-state': noAsyncMethodsOnState,
'no-var-declaration-in-jsx': notVarDeclarationInJSX,
'no-var-name-same-as-prop-name': noVarNameSameAsPropName,
'jsx-callback-arrow-function': jsxCallbackArrowFunction,
'no-var-name-same-as-state-property': noVarNameSameAsStateProperty,
'only-default-function-and-imports': onlyDefaultFunctionAndImports,
'no-conditional-logic-in-component-render':
noConditionalLogicInComponentRender,
'no-var-declaration-or-assignment-in-component':
noVarDeclarationOrAssignmentInComponent,
};
69 changes: 69 additions & 0 deletions packages/eslint-plugin/src/rules/static-control-flow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Rule } from 'eslint';
import * as types from '@babel/types';
import isMitosisPath from '../helpers/isMitosisPath';

export const staticControlFlow: Rule.RuleModule = {
create(context) {
if (!isMitosisPath(context.getFilename())) return {};

return {
VariableDeclarator(node) {
if (types.isVariableDeclarator(node)) {
if (
types.isObjectPattern(node.id) &&
types.isIdentifier(node.init) &&
node.init.name === 'state'
) {
context.report({
node: node as any,
message:
'Destructuring the state object is currently not supported',
});
}
}
},

CallExpression(node) {
if (types.isCallExpression(node)) {
if (
types.isIdentifier(node.callee) &&
node.callee.name === 'useEffect'
) {
const useEffectMessage =
'Only useEffect with an empty array second argument is allowed. E.g. useEffect(...) must be useEffect(..., [])';
const secondArg = node.arguments[1];
if (
!(
secondArg &&
types.isArrayExpression(secondArg) &&
secondArg.elements.length === 0
)
) {
context.report({
node: node,
message: useEffectMessage,
});
}
}
}
},

JSXExpressionContainer(node) {
if (types.isJSXExpressionContainer(node)) {
if (types.isConditionalExpression(node.expression)) {
if (
types.isJSXElement(node.expression.consequent) ||
types.isJSXElement(node.expression.alternate)
) {
context.report({
node: node as any,
message:
'Ternaries around JSX Elements are not currently supported. Instead use binary expressions - e.g. {foo ? <bar /> : <baz />} should be {foo && <bar />}{!foo && <baz />}',
});
}
}
}
},
};
},
};
Loading

0 comments on commit d4f815f

Please sign in to comment.