Add no-deprecated-clone rule
This commit is contained in:
parent
3243af3e07
commit
6285cb9b64
@ -15,7 +15,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha tests --recursive",
|
||||
"lint": "eslint src"
|
||||
"lint": "eslint src tests"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^5.9.0",
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
rules: {},
|
||||
rules: {
|
||||
"no-deprecated-clone": require("./rules/no-deprecated-clone"),
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
// @flow
|
||||
|
||||
"use strict";
|
||||
|
||||
const getReferenceOrigin = require("../utils/get-reference-origin");
|
||||
const getExportName = require("../utils/get-export-name");
|
||||
|
||||
function reportError(context, node, name) {
|
||||
const isMemberExpression = node.type === "MemberExpression";
|
||||
const id = isMemberExpression ? node.property : node;
|
||||
context.report({
|
||||
node: id,
|
||||
message: `t.${name}() is deprecated. Use t.cloneNode() instead.`,
|
||||
fix(fixer) {
|
||||
if (isMemberExpression) {
|
||||
return fixer.replaceText(id, "cloneNode");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
schema: [],
|
||||
fixable: "code",
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
CallExpression(node) {
|
||||
const scope = context.getScope();
|
||||
const origin = getReferenceOrigin(node.callee, scope);
|
||||
|
||||
const report = () => reportError(context, node.callee, origin.name);
|
||||
|
||||
if (!origin) return;
|
||||
|
||||
if (
|
||||
origin.kind === "import" &&
|
||||
(origin.name === "clone" || origin.name === "cloneDeep") &&
|
||||
origin.source === "@babel/types"
|
||||
) {
|
||||
// imported from @babel/types
|
||||
return report();
|
||||
}
|
||||
|
||||
if (
|
||||
origin.kind === "property" &&
|
||||
(origin.path === "clone" || origin.path === "cloneDeep") &&
|
||||
origin.base.kind === "import" &&
|
||||
origin.base.name === "types" &&
|
||||
origin.base.source === "@babel/core"
|
||||
) {
|
||||
// imported from @babel/core
|
||||
return report();
|
||||
}
|
||||
|
||||
if (
|
||||
origin.kind === "property" &&
|
||||
(origin.path === "types.clone" ||
|
||||
origin.path === "types.cloneDeep") &&
|
||||
origin.base.kind === "param" &&
|
||||
origin.base.index === 0
|
||||
) {
|
||||
const { functionNode } = origin.base;
|
||||
const { parent } = functionNode;
|
||||
|
||||
if (parent.type === "CallExpression") {
|
||||
const calleeOrigin = getReferenceOrigin(parent.callee, scope);
|
||||
if (
|
||||
calleeOrigin &&
|
||||
calleeOrigin.kind === "import" &&
|
||||
calleeOrigin.name === "declare" &&
|
||||
calleeOrigin.source === "@babel/helper-plugin-utils"
|
||||
) {
|
||||
// Using "declare" from "@babel/helper-plugin-utils"
|
||||
return report();
|
||||
}
|
||||
} else {
|
||||
const exportName = getExportName(functionNode);
|
||||
|
||||
if (exportName === "default" || exportName === "module.exports") {
|
||||
// export default function ({ types: t }) {}
|
||||
// module.exports = function ({ types: t }) {}
|
||||
return report();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = function getExportName(node) {
|
||||
const { parent } = node;
|
||||
|
||||
if (parent.type === "ExportDefaultDeclaration") {
|
||||
return "default";
|
||||
}
|
||||
|
||||
if (parent.type === "ExportNamedDeclaration") {
|
||||
return node.id.name;
|
||||
}
|
||||
|
||||
if (
|
||||
parent.type === "AssignmentExpression" &&
|
||||
parent.left.type === "MemberExpression" &&
|
||||
parent.left.object.type === "Identifier" &&
|
||||
parent.left.object.name === "module" &&
|
||||
parent.left.property.type === "Identifier" &&
|
||||
parent.left.property.name === "exports"
|
||||
) {
|
||||
return "module.exports";
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,142 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = getReferenceOrigin;
|
||||
|
||||
/*::
|
||||
type ReferenceOriginImport = { kind: "import", source: string, name: string };
|
||||
type ReferenceOriginParam = {
|
||||
kind: "param",
|
||||
index: number,
|
||||
functionNode: Node,
|
||||
};
|
||||
|
||||
type ReferenceOrigin =
|
||||
| ReferenceOriginImport
|
||||
| ReferenceOriginParam
|
||||
| { kind: "import *", source: string }
|
||||
| {
|
||||
kind: "property",
|
||||
base: ReferenceOriginImport | ReferenceOriginParam,
|
||||
path: string,
|
||||
name: string,
|
||||
};
|
||||
*/
|
||||
|
||||
// Given a node and a context, returns a description of where its value comes
|
||||
// from.
|
||||
// It resolves imports, parameters of exported functions and property accesses.
|
||||
// See the ReferenceOrigin type for more informations.
|
||||
function getReferenceOrigin(node, scope) /*: ?ReferenceOrigin */ {
|
||||
if (node.type === "Identifier") {
|
||||
const variable = getVariableDefinition(node.name, scope);
|
||||
if (!variable) return null;
|
||||
|
||||
const definition = variable.definition;
|
||||
const defNode = definition.node;
|
||||
|
||||
if (definition.type === "ImportBinding") {
|
||||
if (defNode.type === "ImportSpecifier") {
|
||||
return {
|
||||
kind: "import",
|
||||
source: definition.parent.source.value,
|
||||
name: defNode.imported.name,
|
||||
};
|
||||
}
|
||||
if (defNode.type === "ImportNamespaceSpecifier") {
|
||||
return {
|
||||
kind: "import *",
|
||||
source: definition.parent.source.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type === "Variable" && defNode.init) {
|
||||
const origin = getReferenceOrigin(defNode.init, variable.scope);
|
||||
return origin && patternToProperty(definition.name, origin);
|
||||
}
|
||||
|
||||
if (definition.type === "Parameter") {
|
||||
return patternToProperty(definition.name, {
|
||||
kind: "param",
|
||||
index: definition.index,
|
||||
functionNode: definition.node,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (node.type === "MemberExpression" && !node.computed) {
|
||||
const origin = getReferenceOrigin(node.object, scope);
|
||||
return origin && addProperty(origin, node.property.name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getVariableDefinition(name, scope) {
|
||||
let currentScope = scope;
|
||||
do {
|
||||
const variable = currentScope.set.get(name);
|
||||
if (variable && variable.defs[0]) {
|
||||
return { scope: currentScope, definition: variable.defs[0] };
|
||||
}
|
||||
} while ((currentScope = currentScope.upper));
|
||||
}
|
||||
|
||||
function patternToProperty(id, base) {
|
||||
const path = getPatternPath(id);
|
||||
return path && path.reduce(addProperty, base);
|
||||
}
|
||||
|
||||
// Adds a property to a given origin. If it was a namespace import it becomes
|
||||
// a named import, so that `import * as x from "foo"; x.bar` and
|
||||
// `import { bar } from "foo"` have the same origin.
|
||||
function addProperty(origin, name) {
|
||||
if (origin.kind === "import *") {
|
||||
return {
|
||||
kind: "import",
|
||||
source: origin.source,
|
||||
name,
|
||||
};
|
||||
}
|
||||
if (origin.kind === "property") {
|
||||
return {
|
||||
kind: "property",
|
||||
base: origin.base,
|
||||
path: origin.path + "." + name,
|
||||
name,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "property",
|
||||
base: origin,
|
||||
path: name,
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
// if "node" is c of { a: { b: c } }, the result is ["a","b"]
|
||||
function getPatternPath(node) {
|
||||
let current = node;
|
||||
const path = [];
|
||||
|
||||
// Unshift keys to path while going up
|
||||
do {
|
||||
const property = current.parent;
|
||||
if (
|
||||
property.type === "ArrayPattern" ||
|
||||
property.type === "AssignmentPattern" ||
|
||||
property.computed
|
||||
) {
|
||||
// These nodes are not supported.
|
||||
return null;
|
||||
}
|
||||
if (property.type === "Property") {
|
||||
path.unshift(property.key.name);
|
||||
} else {
|
||||
// The destructuring pattern is finished
|
||||
break;
|
||||
}
|
||||
} while ((current = current.parent.parent));
|
||||
|
||||
return path;
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
|
||||
const rule = require("../../src/rules/no-deprecated-clone");
|
||||
const { RuleTester } = require("eslint");
|
||||
|
||||
const cloneError = "t.clone() is deprecated. Use t.cloneNode() instead.";
|
||||
const cloneDeepError =
|
||||
"t.cloneDeep() is deprecated. Use t.cloneNode() instead.";
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: { sourceType: "module" },
|
||||
});
|
||||
|
||||
ruleTester.run("no-deprecated-clone", rule, {
|
||||
valid: [
|
||||
`_.clone(obj)`,
|
||||
`_.cloneDeep(obj)`,
|
||||
`import * as t from "lib"; t.clone();`,
|
||||
`import * as t from "lib"; t.cloneDeep();`,
|
||||
`function f(_) { _.types.clone(); }`,
|
||||
`function f(_) { _.types.cloneDeep(); }`,
|
||||
`import * as t from "@babel/types"; t.cloneNode();`,
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `import { clone } from "@babel/types"; clone();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import { cloneDeep } from "@babel/types"; cloneDeep();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import { clone } from "@babel/types"; var clone2 = clone; clone2();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import { cloneDeep } from "@babel/types"; var cloneDeep2 = cloneDeep; cloneDeep2();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import * as t from "@babel/types"; t.clone();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import * as t from "@babel/types"; t.cloneDeep();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import * as t from "@babel/types"; var { clone } = t; clone();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import * as t from "@babel/types"; var { cloneDeep } = t; cloneDeep();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import { clone as c } from "@babel/types"; c();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import { cloneDeep as cD } from "@babel/types"; cD();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import * as babel from "@babel/core"; babel.types.clone();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import * as babel from "@babel/core"; babel.types.cloneDeep();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import { types } from "@babel/core"; types.clone();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import { types } from "@babel/core"; types.cloneDeep();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import { types as t } from "@babel/core"; t.clone();`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import { types as t } from "@babel/core"; t.cloneDeep();`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `export default function plugin(babel) { babel.types.clone() }`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `export default function plugin(babel) { babel.types.cloneDeep() }`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `export default function plugin({ types }) { types.clone() }`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `export default function plugin({ types }) { types.cloneDeep() }`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `export default function plugin({ types: t }) { t.clone() }`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `export default function plugin({ types: t }) { t.cloneDeep() }`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `export default ({ types }) => { types.clone() }`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `export default ({ types }) => { types.cloneDeep() }`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `module.exports = function plugin({ types }) { types.clone() }`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `module.exports = function plugin({ types }) { types.cloneDeep() }`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
{
|
||||
code: `import { declare } from "@babel/helper-plugin-utils"; declare(({ types }) => { types.clone() });`,
|
||||
errors: [cloneError],
|
||||
},
|
||||
{
|
||||
code: `import { declare } from "@babel/helper-plugin-utils"; declare(({ types }) => { types.cloneDeep() });`,
|
||||
errors: [cloneDeepError],
|
||||
},
|
||||
],
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user