diff --git a/packages/eslint-plugin-rax-runtime-miniapp/.eslintrc.js b/packages/eslint-plugin-rax-runtime-miniapp/.eslintrc.js new file mode 100644 index 00000000..2f33f7a7 --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/.eslintrc.js @@ -0,0 +1,19 @@ +"use strict"; + +module.exports = { + root: true, + extends: [ + "eslint:recommended", + "plugin:eslint-plugin/recommended", + "plugin:node/recommended", + ], + env: { + node: true, + }, + overrides: [ + { + files: ["tests/**/*.js"], + env: { mocha: true }, + }, + ], +}; diff --git a/packages/eslint-plugin-rax-runtime-miniapp/README.md b/packages/eslint-plugin-rax-runtime-miniapp/README.md new file mode 100644 index 00000000..de42df3b --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/README.md @@ -0,0 +1,48 @@ +# eslint-plugin-rax-runtime-miniapp + +rax 运行时 eslint 插件 + +## Installation + +You'll first need to install [ESLint](https://eslint.org/): + +```sh +npm i eslint --save-dev +``` + +Next, install `eslint-plugin-rax-runtime-miniapp`: + +```sh +npm install eslint-plugin-rax-runtime-miniapp --save-dev +``` + +## Usage + +Add `rax-runtime-miniapp` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: + +```json +{ + "plugins": [ + "rax-runtime-miniapp" + ] +} +``` + + +Then configure the rules you want to use under the rules section. + +```json +{ + "rules": { + "rax-runtime-miniapp/rule-name": 2 + } +} +``` + +## Supported Rules + +* no-inline-styles + +[no-inline-styles](https://github.com/raxjs/miniapp/tree/master/packages/eslint-plugin-rax-runtime-miniapp/docs/rules/no-inline-styles.md) + + diff --git a/packages/eslint-plugin-rax-runtime-miniapp/docs/rules/no-inline-styles.md b/packages/eslint-plugin-rax-runtime-miniapp/docs/rules/no-inline-styles.md new file mode 100644 index 00000000..57af1f82 --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/docs/rules/no-inline-styles.md @@ -0,0 +1,34 @@ +# 不推荐使用内联样式 (no-inline-style) + +在运行时小程序中,内联样式会导致 `setData` 传输的数据体积变大。本规则会检测元素的内联样式,当内联样式的每一项属性或属性值均为变量时,不会报错。 + +## Rule Details + + +Examples of **incorrect** code for this rule: + +```js +function Hello() { + return ( + Hello + ); +} +``` + +```js +function Hello(props) { + return ( + Hello + ); +} +``` + +Examples of **correct** code for this rule: + +```js +function Hello(props) { + return ( + Hello + ); +} +``` diff --git a/packages/eslint-plugin-rax-runtime-miniapp/package.json b/packages/eslint-plugin-rax-runtime-miniapp/package.json new file mode 100644 index 00000000..70210397 --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/package.json @@ -0,0 +1,32 @@ +{ + "name": "eslint-plugin-rax-runtime-miniapp", + "version": "1.0.0", + "description": "rax 运行时 eslint 插件", + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin" + ], + "author": "chenrongyan", + "main": "src/index.js", + "scripts": { + "lint": "eslint .", + "test": "mocha tests --recursive" + }, + "dependencies": { + "requireindex": "^1.1.0" + }, + "devDependencies": { + "eslint": "^7.1.0", + "eslint-plugin-eslint-plugin": "^3.2.0", + "eslint-plugin-node": "^11.0.0", + "mocha": "^9.0.0" + }, + "engines": { + "node": "12.x || 14.x || >= 16" + }, + "peerDependencies": { + "eslint": ">=6" + }, + "license": "ISC" +} diff --git a/packages/eslint-plugin-rax-runtime-miniapp/src/index.js b/packages/eslint-plugin-rax-runtime-miniapp/src/index.js new file mode 100644 index 00000000..6f4d9e62 --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/src/index.js @@ -0,0 +1,10 @@ +/** + * @fileoverview rax 运行时 eslint 插件 + * @author chenrongyan + */ +"use strict"; + +const requireIndex = require("requireindex"); + +// import all rules in src/rules +module.exports.rules = requireIndex(__dirname + "/rules"); diff --git a/packages/eslint-plugin-rax-runtime-miniapp/src/rules/no-inline-styles.js b/packages/eslint-plugin-rax-runtime-miniapp/src/rules/no-inline-styles.js new file mode 100644 index 00000000..0827995b --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/src/rules/no-inline-styles.js @@ -0,0 +1,56 @@ +/** + * @fileoverview 不推荐使用内联样式 + * @author chenrongyan + */ +"use strict"; + +const styleSheet = require('../utils/stylesheet'); +const docUrl = require('../utils/docUrl'); + +const util = require('util'); + +const { StyleSheets } = styleSheet; +const { astHelpers } = styleSheet; + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: "Using inline styles in runtime miniapp is not recommended", + recommended: false, + url: docUrl('no-inline-styles') + }, + fixable: null, + schema: [], + }, + + create(context) { + const styleSheets = new StyleSheets(); + + function reportInlineStyles(inlineStyles) { + if (inlineStyles) { + inlineStyles.forEach((style) => { + if (style) { + const expression = util.inspect(style.expression); + context.report({ + node: style.node, + message: 'Inline style: {{ expression }}', + data: { expression }, + }); + } + }); + } + } + + return { + JSXAttribute: (node) => { + if (astHelpers.isStyleAttribute(node)) { + const styles = astHelpers.collectStyleObjectExpressions(node.value, context); + styleSheets.addObjectExpressions(styles); + } + }, + + 'Program:exit': () => reportInlineStyles(styleSheets.getObjectExpressions()), + } + } +}; diff --git a/packages/eslint-plugin-rax-runtime-miniapp/src/utils/docUrl.js b/packages/eslint-plugin-rax-runtime-miniapp/src/utils/docUrl.js new file mode 100644 index 00000000..9c4f5a88 --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/src/utils/docUrl.js @@ -0,0 +1,7 @@ +/** + * 返回用户可查看的 doc url + */ +module.exports = function docUrl(docName) { + const repoUrl = 'https://github.com/raxjs/miniapp/tree/master/packages/eslint-plugin-rax-runtime-miniapp'; + return `${repoUrl}/docs/rules/${docName}.md`; +} diff --git a/packages/eslint-plugin-rax-runtime-miniapp/src/utils/stylesheet.js b/packages/eslint-plugin-rax-runtime-miniapp/src/utils/stylesheet.js new file mode 100644 index 00000000..0c4e8f77 --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/src/utils/stylesheet.js @@ -0,0 +1,138 @@ +'use strict'; + +/** + * StyleSheets represents the StyleSheets found in the source code. + * @constructor + */ +function StyleSheets() { + this.styleSheets = {}; +} + +/** + * AddObjectexpressions adds an array of expressions to the ObjectExpressions collection + * @param {Array} expressions - an array of expressions containing ObjectExpressions in + * inline styles + */ +StyleSheets.prototype.addObjectExpressions = function (expressions) { + if (!this.objectExpressions) { + this.objectExpressions = []; + } + this.objectExpressions = this.objectExpressions.concat(expressions); +}; + +/** + * GetObjectExpressions returns an array of collected object expressiosn used in inline styles + * @returns {Array} + */ +StyleSheets.prototype.getObjectExpressions = function () { + return this.objectExpressions; +}; + +let currentContent; +const getSourceCode = (node) => currentContent + .getSourceCode(node) + .getText(node); + +const astHelpers = { + isStyleAttribute: function (node) { + return Boolean( + node.type === 'JSXAttribute' + && node.name + && node.name.name + && node.name.name.toLowerCase().includes('style') + ); + }, + + hasArrayOfStyleReferences: function (node) { + return node && Boolean( + node.type === 'JSXExpressionContainer' + && node.expression + && node.expression.type === 'ArrayExpression' + ); + }, + + collectStyleObjectExpressions: function (node, context) { + currentContent = context; + if (astHelpers.hasArrayOfStyleReferences(node)) { + const styleReferenceContainers = node + .expression + .elements; + + return astHelpers.collectStyleObjectExpressionFromContainers( + styleReferenceContainers + ); + } if (node && node.expression) { + return astHelpers.getStyleObjectExpressionFromNode(node.expression); + } + + return []; + }, + + collectStyleObjectExpressionFromContainers: function (nodes) { + let objectExpressions = []; + nodes.forEach((node) => { + objectExpressions = objectExpressions + .concat(astHelpers.getStyleObjectExpressionFromNode(node)); + }); + + return objectExpressions; + }, + + getStyleObjectFromExpression: function (node) { + const obj = {}; + let invalid = false; + if (node.properties && node.properties.length) { + node.properties.forEach((p) => { + if (!p.value || !p.key) { + return; + } + if (p.value.type === 'Literal') { + invalid = true; + obj[p.key.name] = p.value.value; + } else if (p.value.type === 'ConditionalExpression') { + const innerNode = p.value; + if (innerNode.consequent.type === 'Literal' || innerNode.alternate.type === 'Literal') { + invalid = true; + obj[p.key.name] = getSourceCode(innerNode); + } + } else if (p.value.type === 'UnaryExpression' && p.value.operator === '-' && p.value.argument.type === 'Literal') { + invalid = true; + obj[p.key.name] = -1 * p.value.argument.value; + } else if (p.value.type === 'UnaryExpression' && p.value.operator === '+' && p.value.argument.type === 'Literal') { + invalid = true; + obj[p.key.name] = p.value.argument.value; + } + }); + } + return invalid ? { expression: obj, node: node } : undefined; + }, + + getStyleObjectExpressionFromNode: function (node) { + let leftStyleObjectExpression; + let rightStyleObjectExpression; + + if (!node) { + return []; + } + + if (node.type === 'ObjectExpression') { + return [astHelpers.getStyleObjectFromExpression(node)]; + } + + switch (node.type) { + case 'LogicalExpression': + leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.left); + rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.right); + return [].concat(leftStyleObjectExpression).concat(rightStyleObjectExpression); + case 'ConditionalExpression': + leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.consequent); + rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.alternate); + return [].concat(leftStyleObjectExpression).concat(rightStyleObjectExpression); + default: + return []; + } + }, +} + +module.exports.astHelpers = astHelpers; +module.exports.StyleSheets = StyleSheets; \ No newline at end of file diff --git a/packages/eslint-plugin-rax-runtime-miniapp/tests/src/rules/no-inline-style.js b/packages/eslint-plugin-rax-runtime-miniapp/tests/src/rules/no-inline-style.js new file mode 100644 index 00000000..2c96f75f --- /dev/null +++ b/packages/eslint-plugin-rax-runtime-miniapp/tests/src/rules/no-inline-style.js @@ -0,0 +1,58 @@ +/** + * @fileoverview 不推荐使用内联样式 + * @author chenrongyan + */ +"use strict"; + +const rule = require("../../../src/rules/no-inline-styles"), + RuleTester = require("eslint").RuleTester; + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 10, + ecmaFeatures: { + jsx: true, + }, + }, +}); + +ruleTester.run("no-inline-style", rule, { + valid: [ + `function Hello() { + return ( + Hello + ); + }`, + `function Hello(props) { + return ( + Hello + ); + }` + ], + + invalid: [ + { + code: ` + function Hello() { + return ( + Hello + ); + } + `, + errors: [{ + message: 'Inline style: { backgroundColor: \'#fff\' }' + }], + }, { + code: ` + function Hello(props) { + return ( + Hello + ); + } + `, + errors: [{ + message: 'Inline style: { backgroundColor: \'#fff\' }' + }] + } + ], +});