@putout/eslint 1.0.1 β 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/lib/create-plugin/index.js +129 -0
- package/lib/lint/index.js +45 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ npm i @putout/eslint
|
|
|
13
13
|
|
|
14
14
|
## API
|
|
15
15
|
|
|
16
|
+
### `eslint(options)`
|
|
17
|
+
|
|
16
18
|
**ESLint** begins his work as a formatter when π**Putout** done his transformations. That's why it used a lot in different parts of application, for testing purpose and using **API** in a simplest possible way. You can access it with:
|
|
17
19
|
|
|
18
20
|
```js
|
|
@@ -67,6 +69,114 @@ const [source, places] = await eslint({
|
|
|
67
69
|
|
|
68
70
|
It is disabled by default, because **ESLint** always runs after π**Putout** transformations, so there is no need to traverse tree again.
|
|
69
71
|
|
|
72
|
+
### `createPlugin(options)`
|
|
73
|
+
|
|
74
|
+
You can also simplify creating of plugins for **ESLint** with help of `createPlugin`.
|
|
75
|
+
π**Putout**-based **ESLint** plugin are highly inspired by [**Putout Plugins API**](https://github.com/coderaiser/putout/tree/master/packages/engine-runner#readme) of [**Includer**](https://github.com/coderaiser/putout/tree/master/packages/engine-runner#includer).
|
|
76
|
+
|
|
77
|
+
So it must contain classic `4` methods:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
module.exports.report = () => 'debugger statement should not be used';
|
|
81
|
+
|
|
82
|
+
module.exports.fix = (path) => {
|
|
83
|
+
return '';
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
module.exports.include = () => [
|
|
87
|
+
'DebuggerStatement',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
module.exports.filter = (path) => {
|
|
91
|
+
return true;
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The main difference with [Includer](https://github.com/coderaiser/putout/tree/master/packages/engine-runner#includer) is:
|
|
96
|
+
|
|
97
|
+
- `fix` works with text;
|
|
98
|
+
- `include` does not support π¦[PutoutScript](https://github.com/coderaiser/putout/blob/master/docs/putout-script.md#-putoutscript);
|
|
99
|
+
- there is no `exclude`;
|
|
100
|
+
|
|
101
|
+
Take a look at more sophisticated example, rule [`remove-duplicate-extensions`](https://github.com/coderaiser/putout/tree/master/packages/eslint-plugin-putout/lib/remove-duplicate-extensions#readme):
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
const getValue = ({source}) => source?.value;
|
|
105
|
+
|
|
106
|
+
module.exports.report = () => 'Avoid duplicate extensions in relative imports';
|
|
107
|
+
module.exports.include = () => [
|
|
108
|
+
'ImportDeclaration',
|
|
109
|
+
'ImportExpression',
|
|
110
|
+
'ExportAllDeclaration',
|
|
111
|
+
'ExportNamedDeclaration',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
module.exports.fix = ({text}) => {
|
|
115
|
+
return text.replace('.js.js', '.js');
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
module.exports.filter = ({node}) => {
|
|
119
|
+
const value = getValue(node);
|
|
120
|
+
return /\.js\.js/.test(value);
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
To use it just add couple lines to your main plugin file:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const {createPlugin} = require('@putout/eslint/create-plugin');
|
|
128
|
+
|
|
129
|
+
const createRule = (a) => ({
|
|
130
|
+
[a]: createPlugin(require(`./${a}`)),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
module.exports.rules = {
|
|
134
|
+
...createRule('remove-duplicate-extensions'),
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or just:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const {createPlugin} = require('@putout/eslint/create-plugin');
|
|
142
|
+
|
|
143
|
+
module.exports.rules = {
|
|
144
|
+
'remove-duplicate-extensions': createPlugin(require('./remove-duplicate-extensions')),
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `lint(source, {fix, plugins, options, filename})`
|
|
149
|
+
|
|
150
|
+
When you need to run **ESLint** with one plugin (*rule*), just use `lint` it will do the thing.
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
const lint = require('@putout/eslint/lint');
|
|
154
|
+
const removeDebugger = require('./remove-debugger');
|
|
155
|
+
|
|
156
|
+
const [code, places] = lint('debugger', {
|
|
157
|
+
fix: true, // default
|
|
158
|
+
plugins: [
|
|
159
|
+
['remove-debugger', createPlugin(removeDebugger)],
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
When you want to skip plugins, and just provide `options` and `filename` you can:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
const lint = require('@putout/eslint/lint');
|
|
168
|
+
const removeDebugger = require('./remove-debugger');
|
|
169
|
+
|
|
170
|
+
const [code, places] = lint('debugger', {
|
|
171
|
+
filename: 'index.js',
|
|
172
|
+
options: [{
|
|
173
|
+
rules: {
|
|
174
|
+
semi: 'error',
|
|
175
|
+
},
|
|
176
|
+
}],
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
70
180
|
## License
|
|
71
181
|
|
|
72
182
|
MIT
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const prepare = (plugin, context, options) => (node) => {
|
|
4
|
+
const {filter, report} = plugin;
|
|
5
|
+
|
|
6
|
+
const source = context.getSourceCode();
|
|
7
|
+
const filename = context.getFilename();
|
|
8
|
+
const getText = source.getText.bind(source);
|
|
9
|
+
const getCommentsBefore = source.getCommentsBefore.bind(source);
|
|
10
|
+
const getCommentsAfter = source.getCommentsAfter.bind(source);
|
|
11
|
+
const getCommentsInside = source.getCommentsInside.bind(source);
|
|
12
|
+
|
|
13
|
+
const getSpacesBeforeNode = createGetSpacesBeforeNode({
|
|
14
|
+
getText,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const getSpacesAfterNode = createGetSpacesAfterNode({
|
|
18
|
+
getText,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const text = getText(node);
|
|
22
|
+
|
|
23
|
+
const result = filter({
|
|
24
|
+
text,
|
|
25
|
+
node,
|
|
26
|
+
options,
|
|
27
|
+
getText,
|
|
28
|
+
getCommentsBefore,
|
|
29
|
+
getCommentsAfter,
|
|
30
|
+
getCommentsInside,
|
|
31
|
+
getSpacesBeforeNode,
|
|
32
|
+
getSpacesAfterNode,
|
|
33
|
+
filename,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!result)
|
|
37
|
+
return;
|
|
38
|
+
|
|
39
|
+
const fix = prepareFix(plugin.fix, {
|
|
40
|
+
filename,
|
|
41
|
+
node,
|
|
42
|
+
text,
|
|
43
|
+
getText,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
context.report({
|
|
47
|
+
node,
|
|
48
|
+
message: report(node),
|
|
49
|
+
fix,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const prepareFix = (fix, {node, text, getText, filename}) => (fixer) => {
|
|
54
|
+
const fixed = fix({
|
|
55
|
+
node,
|
|
56
|
+
text,
|
|
57
|
+
getText,
|
|
58
|
+
filename,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return [
|
|
62
|
+
fixer.replaceText(node, fixed),
|
|
63
|
+
];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
module.exports.createPlugin = (plugin) => {
|
|
67
|
+
const meta = getMeta(plugin);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
meta,
|
|
71
|
+
create(context) {
|
|
72
|
+
const {options} = context;
|
|
73
|
+
const prepared = prepare(plugin, context, options);
|
|
74
|
+
const names = plugin.include({options});
|
|
75
|
+
|
|
76
|
+
return getTraversers(names, prepared);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function getMeta(plugin) {
|
|
82
|
+
const {
|
|
83
|
+
type = 'layout',
|
|
84
|
+
recommended = true,
|
|
85
|
+
fixable = 'whitespace',
|
|
86
|
+
} = plugin;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
type,
|
|
90
|
+
docs: {
|
|
91
|
+
recommended,
|
|
92
|
+
},
|
|
93
|
+
schema: {},
|
|
94
|
+
fixable,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getTraversers(names, plugin) {
|
|
99
|
+
const traversers = {};
|
|
100
|
+
|
|
101
|
+
for (const name of names)
|
|
102
|
+
traversers[name] = plugin;
|
|
103
|
+
|
|
104
|
+
return traversers;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const createGetSpacesBeforeNode = ({getText}) => (node, text = getText(node)) => {
|
|
108
|
+
let spaces = '';
|
|
109
|
+
let i = 0;
|
|
110
|
+
|
|
111
|
+
while (!spaces || /^[ \n]+$/.test(spaces))
|
|
112
|
+
spaces = getText(node, ++i)
|
|
113
|
+
.replace(text, '');
|
|
114
|
+
|
|
115
|
+
return spaces.slice(1);
|
|
116
|
+
};
|
|
117
|
+
module.exports.createGetSpaceBeforeNode = createGetSpacesBeforeNode;
|
|
118
|
+
|
|
119
|
+
const createGetSpacesAfterNode = ({getText}) => (node, {text = getText(node)}) => {
|
|
120
|
+
let spaces = '';
|
|
121
|
+
let i = 0;
|
|
122
|
+
|
|
123
|
+
while (!spaces || /^[ \n;]+$/.test(spaces))
|
|
124
|
+
spaces = getText(node, 0, ++i)
|
|
125
|
+
.replace(text, '');
|
|
126
|
+
|
|
127
|
+
return spaces.slice(0, -1);
|
|
128
|
+
};
|
|
129
|
+
module.exports.createGetSpacesAfterNode = createGetSpacesAfterNode;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {Linter} = require('eslint');
|
|
4
|
+
const {convertToPlace} = require('../eslint.js');
|
|
5
|
+
|
|
6
|
+
module.exports.lint = (source, {fix = true, plugins, filename, options = []}) => {
|
|
7
|
+
const linter = new Linter({
|
|
8
|
+
configType: 'flat',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const allOptions = [];
|
|
12
|
+
|
|
13
|
+
if (plugins) {
|
|
14
|
+
const [name, plugin] = plugins[0];
|
|
15
|
+
allOptions.push({
|
|
16
|
+
rules: {
|
|
17
|
+
[`${name}/plugin`]: 'error',
|
|
18
|
+
},
|
|
19
|
+
plugins: {
|
|
20
|
+
[name]: {
|
|
21
|
+
rules: {
|
|
22
|
+
plugin,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
allOptions.push(...options);
|
|
30
|
+
|
|
31
|
+
const mainOptions = {};
|
|
32
|
+
|
|
33
|
+
if (filename)
|
|
34
|
+
mainOptions.filename = filename;
|
|
35
|
+
|
|
36
|
+
if (!fix) {
|
|
37
|
+
const places = linter.verify(source, allOptions, mainOptions).map(convertToPlace);
|
|
38
|
+
return [source, places];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const {output, messages} = linter.verifyAndFix(source, allOptions, mainOptions);
|
|
42
|
+
|
|
43
|
+
return [output, messages.map(convertToPlace)];
|
|
44
|
+
};
|
|
45
|
+
|
package/package.json
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@putout/eslint",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
|
|
6
6
|
"description": "Wrapper that simplifies ESLint API and makes it compatible with πPutout",
|
|
7
7
|
"homepage": "https://github.com/coderaiser/putout/tree/master/packages/eslint#readme",
|
|
8
|
-
"main": "lib/eslint.js",
|
|
8
|
+
"main": "./lib/eslint.js",
|
|
9
9
|
"commitType": "colon",
|
|
10
10
|
"release": false,
|
|
11
11
|
"tag": false,
|
|
12
12
|
"changelog": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./lib/eslint.js",
|
|
15
|
+
"./create-plugin": "./lib/create-plugin/index.js",
|
|
16
|
+
"./lint": "./lib/lint/index.js"
|
|
17
|
+
},
|
|
13
18
|
"repository": {
|
|
14
19
|
"type": "git",
|
|
15
20
|
"url": "git://github.com/coderaiser/putout.git"
|