Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/runtime eslint plugin #207

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions packages/eslint-plugin-rax-runtime-miniapp/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -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 },
},
],
};
48 changes: 48 additions & 0 deletions packages/eslint-plugin-rax-runtime-miniapp/README.md
Original file line number Diff line number Diff line change
@@ -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)


Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 不推荐使用内联样式 (no-inline-style)

在运行时小程序中,内联样式会导致 `setData` 传输的数据体积变大。本规则会检测元素的内联样式,当内联样式的每一项属性或属性值均为变量时,不会报错。

## Rule Details


Examples of **incorrect** code for this rule:

```js
function Hello() {
return (
<Text style={{ backgroundColor: '#fff' }}>Hello</Text>
);
}
```

```js
function Hello(props) {
return (
<Text style={{ height: props.height, backgroundColor: '#fff' }}>Hello</Text>
);
}
```

Examples of **correct** code for this rule:

```js
function Hello(props) {
return (
<Text style={{ height: props.height }}>Hello</Text>
);
}
```
32 changes: 32 additions & 0 deletions packages/eslint-plugin-rax-runtime-miniapp/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
10 changes: 10 additions & 0 deletions packages/eslint-plugin-rax-runtime-miniapp/src/index.js
Original file line number Diff line number Diff line change
@@ -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");
Original file line number Diff line number Diff line change
@@ -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()),
}
}
};
Original file line number Diff line number Diff line change
@@ -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`;
}
138 changes: 138 additions & 0 deletions packages/eslint-plugin-rax-runtime-miniapp/src/utils/stylesheet.js
Original file line number Diff line number Diff line change
@@ -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;
Loading