Skip to content

Commit a091907

Browse files
vitorbalnzakas
authored andcommitted
New: Initial commit
* add new-rule-format transform and initial repo structure * add note to new-rule-format regarding ES6 modules not being supported * New: add simple CLI and eslint-release tool * Docs: remove "npm run" usage example from README * Update: remove ES6 syntax * Chore: remove es6-related npm packages that are not needed anymore * Update: Use mocha+chai for tests instead of Jest * Update: move transform call into it() so errors are caught properly * Fix: handle rules that have the definition wrapped in a call expression * Update: extract helper methods out of the main exported function * Update: remove unecessary license info and obsolete jsdoc param
1 parent 21201a2 commit a091907

28 files changed

+838
-2
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
trim_trailing_whitespace = true
7+
end_of_line = lf
8+
insert_final_newline = true
9+
10+
[package.json]
11+
indent_style = space
12+
indent_size = 2

.eslintrc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
env:
2+
node: true
3+
extends: "eslint-config-eslint"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules
2+
npm-debug.log
3+
.DS_Store

LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
ESLint Transforms
2+
Copyright jQuery Foundation and other contributors, https://jquery.org/
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in
12+
all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.

README.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,50 @@
1-
# eslint-transforms
2-
Codemods for the ESLint ecosystem
1+
# ESLint Transforms
32

3+
A collection of jscodeshift transforms to help upgrade ESLint rules to new versions of [ESLint](https://github.com/eslint/eslint).
4+
5+
## Installation
6+
7+
You can install the ESLint transforms tool using [npm](https://npmjs.com):
8+
9+
```
10+
$ npm install eslint-transforms --save-dev
11+
```
12+
13+
## Usage
14+
15+
```
16+
$ eslint-transforms <transform-name> <path>
17+
```
18+
19+
Where:
20+
21+
`transform-name` - Name of the transform you want to run (e.g. `new-rule-format`). See the [transforms](#transforms) section below for a list of available transforms.
22+
23+
`path` - Files or directory to transform.
24+
25+
26+
For more information on jscodeshift, check their official [docs](https://github.com/facebook/jscodeshift).
27+
28+
## Transforms
29+
30+
### new-rule-format
31+
32+
**Please note**: The transform will not work for rules that use ES6 modules syntax.
33+
34+
Transform that migrates an ESLint rule definition from the old format:
35+
36+
```javascript
37+
module.exports = function(context) { ... }
38+
```
39+
40+
to the new format, introduced in ESLint 2.10.0:
41+
42+
```javascript
43+
module.exports = {
44+
meta: {
45+
docs: {},
46+
schema: []
47+
},
48+
create: function(context) { ... }
49+
};
50+
```

bin/eslint-transforms.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env node
2+
3+
"use strict";
4+
5+
var nodeCLI = require("shelljs-nodecli");
6+
7+
var argv = process.argv.slice(2);
8+
var args = argv.slice(1);
9+
var transform = argv[0];
10+
11+
nodeCLI.exec(
12+
"jscodeshift",
13+
"-t", "./lib/" + transform + "/" + transform + ".js",
14+
args.join(" ")
15+
);
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/**
2+
* @fileoverview Transform that migrates an ESLint rule definitions from the old format:
3+
*
4+
* ```
5+
* module.exports = function(context) { ... }
6+
* ```
7+
*
8+
* to the new format:
9+
*
10+
* ```
11+
* module.exports = {
12+
* meta: {
13+
* docs: {},
14+
* schema: []
15+
* },
16+
* create: function(context) { ... }
17+
* };
18+
* ```
19+
* @author Vitor Balocco
20+
*/
21+
22+
"use strict";
23+
24+
var j = require("jscodeshift");
25+
26+
//------------------------------------------------------------------------------
27+
// Helpers
28+
//------------------------------------------------------------------------------
29+
30+
/**
31+
* Returns `true` if the rule is already in the new format
32+
*
33+
* @param {Object} rootNode - where to look for the rule definition
34+
* @returns {Boolean} `true` if rule is already in the new format
35+
*/
36+
function isAlreadyInNewFormat(rootNode) {
37+
// If there's already a module.exports.meta property, we assume the rule
38+
// is already in the new format.
39+
return rootNode
40+
.find(j.Property)
41+
.filter(function(node) {
42+
return (
43+
node.value.key.name === "meta" &&
44+
node.parent.value.type === "ObjectExpression" &&
45+
node.parent.parent.value.type === "AssignmentExpression" &&
46+
node.parent.parent.value.left.type === "MemberExpression" &&
47+
node.parent.parent.value.left.object.name === "module" &&
48+
node.parent.parent.value.left.property.name === "exports"
49+
);
50+
})
51+
.size() > 0;
52+
}
53+
54+
/**
55+
* Checks if the node passed is a function expression or an arrow function expression
56+
*
57+
* @param {Object} node - node to check
58+
* @returns {Boolean} - `true` if node is a function or arrow function expression
59+
*/
60+
function isFunctionOrArrowFunctionExpression(node) {
61+
return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
62+
}
63+
64+
/**
65+
* Checks if the node passed can be an "old format" rule definition
66+
*
67+
* @param {Object} node - node to check
68+
* @returns {Boolean} - `true` if node looks like an "old format" rule definition
69+
*/
70+
function isOldFormatRuleDefinition(node) {
71+
return isFunctionOrArrowFunctionExpression(node) || node.type === "CallExpression";
72+
}
73+
74+
/**
75+
* Returns the node in `rootNode` that is the rule definition in the old format,
76+
* which will be in the format:
77+
* module.exports = function(context) { ... };
78+
*
79+
* @param {Object} rootNode - where to look for the rule definition node
80+
* @returns {Object} node - rule definition expression node
81+
*/
82+
function getOldFormatRuleDefinition(rootNode) {
83+
return rootNode
84+
.find(j.AssignmentExpression)
85+
.filter(function(node) {
86+
return (
87+
node.value.left.type === "MemberExpression" &&
88+
node.value.left.property.name === "exports" &&
89+
node.value.left.object.name === "module" &&
90+
isOldFormatRuleDefinition(node.value.right)
91+
);
92+
});
93+
}
94+
95+
/**
96+
* Returns the node in `rootNode` that is the schema definition in the old format,
97+
* which will be in the format:
98+
* module.exports.schema = [ ... ];
99+
*
100+
* @param {Object} rootNode - where to look for the rule definition node
101+
* @returns {Object} node - rule definition expression node
102+
*/
103+
function getOldFormatSchemaDefinition(rootNode) {
104+
return rootNode
105+
.find(j.AssignmentExpression)
106+
.filter(function(node) {
107+
return (
108+
node.value.left.type === "MemberExpression" &&
109+
node.value.left.property.name === "schema" &&
110+
node.value.left.object.type === "MemberExpression" &&
111+
node.value.left.object.object.name === "module" &&
112+
node.value.left.object.property.name === "exports"
113+
);
114+
});
115+
}
116+
117+
/**
118+
* Creates the object expression node that will be the `meta` property of this rule
119+
*
120+
* @param {Object} schemaNode - node that was the schema definition in the old rule format
121+
* @param {Object} schemaNodeComments - comments that were above the old schema node
122+
* @param {Boolean} isRuleFixable - `true` if the rule is fixable
123+
* @returns {Object} ObjectExpression node
124+
*/
125+
function createMetaObjectExpression(schemaNode, schemaNodeComments, isRuleFixable) {
126+
var properties = [
127+
// For docs, create just an empty object
128+
j.property("init", j.identifier("docs"), j.objectExpression([]))
129+
];
130+
131+
if (isRuleFixable) {
132+
properties.push(
133+
j.property("init", j.identifier("fixable"), j.literal("code"))
134+
);
135+
}
136+
137+
// The schema definition may not exist in some plugins
138+
if (schemaNode) {
139+
var schemaNodeProperty = j.property("init", j.identifier("schema"), schemaNode);
140+
// Restore comments that were removed when the old format node was removed
141+
schemaNodeProperty.comments = schemaNodeComments;
142+
properties.push(schemaNodeProperty);
143+
}
144+
145+
return j.objectExpression(properties);
146+
}
147+
148+
/**
149+
* Creates the `exports` expression that wil contain the rule definition in the new format
150+
*
151+
* @param {Object} ruleDefinitionNode - node that was the rule definition in the old rule format
152+
* @param {Object} ruleDefinitionNodeComments - comments that were above the old schema rule definition
153+
* @param {Object} ruleMetaDefinitionNode - node that will be the meta definition expression
154+
* @returns {Object} ExpressionStatement
155+
*/
156+
function createExportsExpression(ruleDefinitionNode, ruleDefinitionNodeComments, ruleMetaDefinitionNode) {
157+
var exportsExpression = j.expressionStatement(
158+
j.assignmentExpression(
159+
"=",
160+
j.memberExpression(j.identifier("module"), j.identifier("exports"), false),
161+
j.objectExpression([
162+
j.property("init", j.identifier("meta"), ruleMetaDefinitionNode),
163+
j.property("init", j.identifier("create"), ruleDefinitionNode)
164+
])
165+
)
166+
);
167+
168+
// Restore comments that were removed when the old format node was removed
169+
exportsExpression.comments = ruleDefinitionNodeComments;
170+
171+
return exportsExpression;
172+
}
173+
174+
//------------------------------------------------------------------------------
175+
// Transform Definition
176+
//------------------------------------------------------------------------------
177+
178+
/**
179+
* @param {Object} fileInfo - holds information about the currently processed file.
180+
* @returns {String} the new source code, after being transformed.
181+
*/
182+
module.exports = function(fileInfo) {
183+
var root = j(fileInfo.source);
184+
185+
if (isAlreadyInNewFormat(root)) {
186+
// don't do anything and return
187+
return root.toSource();
188+
}
189+
190+
// for most plugins, the old format should be:
191+
// module.exports = function(context) { ... }
192+
// but maybe some plugins are using a diferent variable name instead of `context`
193+
var ruleDefinitionExpression = getOldFormatRuleDefinition(root).get().value.right;
194+
var identifierNameForContextObject = "context";
195+
if (ruleDefinitionExpression.params && ruleDefinitionExpression.params.length > 0) {
196+
identifierNameForContextObject = ruleDefinitionExpression.params[0].name;
197+
}
198+
199+
// If the rule has a call for context.report and a property `fix` is being passed in,
200+
// then we consider that the rule is fixable:
201+
// context.report({
202+
// ...
203+
// fix: function() { ... }
204+
// });
205+
var isRuleFixable = root
206+
.find(j.Identifier)
207+
.filter(function(node) {
208+
return (
209+
node.value.name === "fix" &&
210+
node.parent.value.type === "Property" &&
211+
node.parent.parent.parent.value.type === "CallExpression" &&
212+
node.parent.parent.parent.value.callee.type === "MemberExpression" &&
213+
node.parent.parent.parent.value.callee.object.name === identifierNameForContextObject &&
214+
node.parent.parent.parent.value.callee.property.name === "report"
215+
);
216+
})
217+
.size() > 0;
218+
219+
var oldFormatSchemaDefinition = getOldFormatSchemaDefinition(root);
220+
var schemaNode, schemaNodeComments;
221+
// The schema definition may not exist in some plugins
222+
if (oldFormatSchemaDefinition.size() > 0) {
223+
schemaNode = getOldFormatSchemaDefinition(root).get().value.right;
224+
// Store the comments too so we can attach it again later
225+
schemaNodeComments = getOldFormatSchemaDefinition(root).get().parent.value.leadingComments;
226+
getOldFormatSchemaDefinition(root).remove();
227+
}
228+
229+
var ruleDefinitionNode = getOldFormatRuleDefinition(root).get().value.right;
230+
// Store the comments too so we can attach it again later
231+
var ruleDefinitionNodeComments = getOldFormatRuleDefinition(root).get().parent.value.leadingComments;
232+
getOldFormatRuleDefinition(root).remove();
233+
234+
// Insert the rule definition in the new format at the end of the file
235+
var newFormat = createExportsExpression(
236+
ruleDefinitionNode,
237+
ruleDefinitionNodeComments,
238+
createMetaObjectExpression(schemaNode, schemaNodeComments, isRuleFixable)
239+
);
240+
241+
root.find(j.Program).get().value.body.push(newFormat);
242+
return root.toSource();
243+
};

package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "eslint-transforms",
3+
"version": "0.0.1",
4+
"description": "Codemods for upgrading eslint rules",
5+
"license": "MIT",
6+
"author": "Vitor Balocco",
7+
"repository": {
8+
"type": "git",
9+
"url": "https://github.com/eslint/eslint-transforms"
10+
},
11+
"engines": {
12+
"node": ">=0.10"
13+
},
14+
"bin": {
15+
"eslint-transforms": "./bin/eslint-transforms.js"
16+
},
17+
"files": [
18+
"bin",
19+
"lib"
20+
],
21+
"scripts": {
22+
"lint": "eslint .",
23+
"test": "npm run lint && mocha ./tests/lib/**/*.js",
24+
"new-rule-format": "node ./bin/eslint-transforms.js -t ./lib/new-rule-format/new-rule-format.js"
25+
},
26+
"devDependencies": {
27+
"chai": "^3.5.0",
28+
"eslint": "^2.9.0",
29+
"eslint-config-eslint": "^3.0.0",
30+
"eslint-release": "^0.5.0",
31+
"mocha": "^2.5.3"
32+
},
33+
"dependencies": {
34+
"jscodeshift": "^0.3.20",
35+
"shelljs-nodecli": "^0.1.1"
36+
},
37+
"keywords": [
38+
"javascript",
39+
"eslint",
40+
"jscodeshift"
41+
]
42+
}

0 commit comments

Comments
 (0)