Merge pull request babel/eslint-plugin-babel#1 from mathieumg/blockscopedvar_export
Added support for experimental export types
This commit is contained in:
@@ -23,6 +23,7 @@ Finally enable all the rules you like to use (remember to disable the originals
|
||||
```json
|
||||
{
|
||||
"rules": {
|
||||
"babel/block-scoped-var": 1,
|
||||
"babel/object-shorthand": 1,
|
||||
"babel/generator-star": 1,
|
||||
"babel/generator-star-spacing": 1,
|
||||
@@ -34,6 +35,7 @@ Finally enable all the rules you like to use (remember to disable the originals
|
||||
|
||||
Each rule cooresponds to a core eslint rule, and has the same options.
|
||||
|
||||
- `babel/block-scoped-var`: doesn't complain about `export x from "mod";` or `export * as x from "mod";`
|
||||
- `babel/object-shorthand`: doesn't fail when using object spread (`...obj`)
|
||||
- `babel/generator-star`: Handles async/await functions correctly
|
||||
- `babel/generator-star-spacing`: Handles async/await functions correctly
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
'block-scoped-var': require('./rules/block-scoped-var'),
|
||||
'object-shorthand': require('./rules/object-shorthand'),
|
||||
'generator-star-spacing': require('./rules/generator-star-spacing'),
|
||||
'generator-star': require('./rules/generator-star'),
|
||||
'new-cap': require('./rules/new-cap')
|
||||
},
|
||||
rulesConfig: {
|
||||
'block-scoped-var': 0,
|
||||
'generator-star-spacing': 0,
|
||||
'generator-star': 0,
|
||||
'object-shorthand': 0,
|
||||
|
||||
340
eslint/babel-eslint-plugin/rules/block-scoped-var.js
Normal file
340
eslint/babel-eslint-plugin/rules/block-scoped-var.js
Normal file
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* @fileoverview Rule to check for "block scoped" variables by binding context
|
||||
* @author Matt DuVall <http://www.mattduvall.com>
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var scopeStack = [];
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines whether an identifier is in declaration position or is a non-declaration reference.
|
||||
* @param {ASTNode} id The identifier.
|
||||
* @param {ASTNode} parent The identifier's parent AST node.
|
||||
* @returns {Boolean} true when the identifier is in declaration position.
|
||||
*/
|
||||
function isDeclaration(id, parent) {
|
||||
switch (parent.type) {
|
||||
case "FunctionDeclaration":
|
||||
case "FunctionExpression":
|
||||
return parent.params.indexOf(id) > -1 || id === parent.id;
|
||||
|
||||
case "VariableDeclarator":
|
||||
return id === parent.id;
|
||||
|
||||
case "CatchClause":
|
||||
return id === parent.param;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an identifier is in property position.
|
||||
* @param {ASTNode} id The identifier.
|
||||
* @param {ASTNode} parent The identifier's parent AST node.
|
||||
* @returns {Boolean} true when the identifier is in property position.
|
||||
*/
|
||||
function isProperty(id, parent) {
|
||||
switch (parent.type) {
|
||||
case "MemberExpression":
|
||||
return id === parent.property && !parent.computed;
|
||||
|
||||
case "Property":
|
||||
return id === parent.key;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a new scope object on the scope stack.
|
||||
* @returns {void}
|
||||
*/
|
||||
function pushScope() {
|
||||
scopeStack.push([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the topmost scope object from the scope stack.
|
||||
* @returns {void}
|
||||
*/
|
||||
function popScope() {
|
||||
scopeStack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares the given names in the topmost scope object.
|
||||
* @param {[String]} names A list of names to declare.
|
||||
* @returns {void}
|
||||
*/
|
||||
function declare(names) {
|
||||
[].push.apply(scopeStack[scopeStack.length - 1], names);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public API
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Declares all relevant identifiers for module imports.
|
||||
* @param {ASTNode} node The AST node representing an import.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function declareImports(node) {
|
||||
declare([node.local.name]);
|
||||
|
||||
if (node.imported && node.imported.name !== node.local.name) {
|
||||
declare([node.imported.name]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares all relevant identifiers for module exports.
|
||||
* @param {ASTNode} node The AST node representing an export.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function declareExports(node) {
|
||||
if (node.exported && node.exported.name) {
|
||||
declare([node.exported.name]);
|
||||
|
||||
if (node.local) {
|
||||
declare([node.local.name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares all relevant identifiers for classes.
|
||||
* @param {ASTNode} node The AST node representing a class.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function declareClass(node) {
|
||||
|
||||
if (node.id) {
|
||||
declare([node.id.name]);
|
||||
}
|
||||
|
||||
pushScope();
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares all relevant identifiers for classes.
|
||||
* @param {ASTNode} node The AST node representing a class.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function declareClassMethod(node) {
|
||||
pushScope();
|
||||
|
||||
declare([node.key.name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add declarations based on the type of node being passed.
|
||||
* @param {ASTNode} node The node containing declarations.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function declareByNodeType(node) {
|
||||
|
||||
var declarations = [];
|
||||
|
||||
switch (node.type) {
|
||||
case "Identifier":
|
||||
declarations.push(node.name);
|
||||
break;
|
||||
|
||||
case "ObjectPattern":
|
||||
node.properties.forEach(function(property) {
|
||||
declarations.push(property.key.name);
|
||||
if (property.value) {
|
||||
declarations.push(property.value.name);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "ArrayPattern":
|
||||
node.elements.forEach(function(element) {
|
||||
if (element) {
|
||||
declarations.push(element.name);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "AssignmentPattern":
|
||||
declareByNodeType(node.left);
|
||||
break;
|
||||
|
||||
case "RestElement":
|
||||
declareByNodeType(node.argument);
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
|
||||
declare(declarations);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds declarations of the function parameters and pushes the scope
|
||||
* @param {ASTNode} node The node containing declarations.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function functionHandler(node) {
|
||||
pushScope();
|
||||
|
||||
node.params.forEach(function(param) {
|
||||
declareByNodeType(param);
|
||||
});
|
||||
|
||||
declare(node.rest ? [node.rest.name] : []);
|
||||
declare(["arguments"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds declaration of the function name in its parent scope then process the function
|
||||
* @param {ASTNode} node The node containing declarations.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function functionDeclarationHandler(node) {
|
||||
declare(node.id ? [node.id.name] : []);
|
||||
functionHandler(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process function declarations and declares its name in its own scope
|
||||
* @param {ASTNode} node The node containing declarations.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function functionExpressionHandler(node) {
|
||||
functionHandler(node);
|
||||
declare(node.id ? [node.id.name] : []);
|
||||
}
|
||||
|
||||
function variableDeclarationHandler(node) {
|
||||
node.declarations.forEach(function(declaration) {
|
||||
declareByNodeType(declaration.id);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
"Program": function() {
|
||||
var scope = context.getScope();
|
||||
scopeStack = [scope.variables.map(function(v) {
|
||||
return v.name;
|
||||
})];
|
||||
|
||||
// global return creates another scope
|
||||
if (context.ecmaFeatures.globalReturn) {
|
||||
scope = scope.childScopes[0];
|
||||
scopeStack.push(scope.variables.map(function(v) {
|
||||
return v.name;
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
"ImportSpecifier": declareImports,
|
||||
"ImportDefaultSpecifier": declareImports,
|
||||
"ImportNamespaceSpecifier": declareImports,
|
||||
|
||||
"ExportSpecifier": declareExports,
|
||||
"ExportDefaultSpecifier": declareExports,
|
||||
"ExportNamespaceSpecifier": declareExports,
|
||||
|
||||
"BlockStatement": function(node) {
|
||||
var statements = node.body;
|
||||
pushScope();
|
||||
statements.forEach(function(stmt) {
|
||||
if (stmt.type === "VariableDeclaration") {
|
||||
variableDeclarationHandler(stmt);
|
||||
} else if (stmt.type === "FunctionDeclaration") {
|
||||
declare([stmt.id.name]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
"VariableDeclaration": function (node) {
|
||||
variableDeclarationHandler(node);
|
||||
},
|
||||
|
||||
"BlockStatement:exit": popScope,
|
||||
|
||||
"CatchClause": function(node) {
|
||||
pushScope();
|
||||
declare([node.param.name]);
|
||||
},
|
||||
"CatchClause:exit": popScope,
|
||||
|
||||
"FunctionDeclaration": functionDeclarationHandler,
|
||||
"FunctionDeclaration:exit": popScope,
|
||||
|
||||
"ClassDeclaration": declareClass,
|
||||
"ClassDeclaration:exit": popScope,
|
||||
|
||||
"ClassExpression": declareClass,
|
||||
"ClassExpression:exit": popScope,
|
||||
|
||||
"MethodDefinition": declareClassMethod,
|
||||
"MethodDefinition:exit": popScope,
|
||||
|
||||
"FunctionExpression": functionExpressionHandler,
|
||||
"FunctionExpression:exit": popScope,
|
||||
|
||||
// Arrow functions cannot have names
|
||||
"ArrowFunctionExpression": functionHandler,
|
||||
"ArrowFunctionExpression:exit": popScope,
|
||||
|
||||
"ForStatement": function() {
|
||||
pushScope();
|
||||
},
|
||||
"ForStatement:exit": popScope,
|
||||
|
||||
"ForInStatement": function() {
|
||||
pushScope();
|
||||
},
|
||||
"ForInStatement:exit": popScope,
|
||||
|
||||
"ForOfStatement": function() {
|
||||
pushScope();
|
||||
},
|
||||
"ForOfStatement:exit": popScope,
|
||||
|
||||
"Identifier": function(node) {
|
||||
var ancestor = context.getAncestors().pop();
|
||||
if (isDeclaration(node, ancestor) || isProperty(node, ancestor) || ancestor.type === "LabeledStatement") {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0, l = scopeStack.length; i < l; i++) {
|
||||
if (scopeStack[i].indexOf(node.name) > -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context.report(node, "\"" + node.name + "\" used outside of binding context.");
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
105
eslint/babel-eslint-plugin/tests/block-scoped-var.js
Normal file
105
eslint/babel-eslint-plugin/tests/block-scoped-var.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* @fileoverview Tests for block-scoped-var rule
|
||||
* @author Matt DuVall <http://www.mattduvall.com>
|
||||
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
|
||||
*/
|
||||
|
||||
var eslint = require("eslint").linter,
|
||||
ESLintTester = require("eslint-tester"),
|
||||
eslintTester = new ESLintTester(eslint);
|
||||
|
||||
eslintTester.addRuleTest("rules/block-scoped-var", {
|
||||
valid: [
|
||||
//original test cases
|
||||
{ code: "function f() { } f(); var exports = { f: f };", ecmaFeatures: {modules: true} },
|
||||
{ code: "var f = () => {}; f(); var exports = { f: f };", ecmaFeatures: {arrowFunctions: true, modules: true} },
|
||||
"!function f(){ f; }",
|
||||
"function f() { } f(); var exports = { f: f };",
|
||||
"function f() { var a, b; { a = true; } b = a; }",
|
||||
"var a; function f() { var b = a; }",
|
||||
"function f(a) { }",
|
||||
"!function(a) { };",
|
||||
"!function f(a) { };",
|
||||
"function f(a) { var b = a; }",
|
||||
"!function f(a) { var b = a; };",
|
||||
"function f() { var g = f; }",
|
||||
"function f() { } function g() { var f = g; }",
|
||||
"function f() { var hasOwnProperty; { hasOwnProperty; } }",
|
||||
"function f(){ a; b; var a, b; }",
|
||||
"function f(){ g(); function g(){} }",
|
||||
{ code: "function myFunc(foo) { \"use strict\"; var { bar } = foo; bar.hello();}", ecmaFeatures: { destructuring: true } },
|
||||
{ code: "function myFunc(foo) { \"use strict\"; var [ bar ] = foo; bar.hello();}", ecmaFeatures: { destructuring: true } },
|
||||
{ code: "function myFunc(...foo) { return foo;}", ecmaFeatures: { restParams: true } },
|
||||
{ code: "var f = () => { var g = f; }", ecmaFeatures: { arrowFunctions: true } },
|
||||
{ code: "class Foo {}\nexport default Foo;", ecmaFeatures: { modules: true, classes: true } },
|
||||
{ code: "new Date", globals: {Date: false} },
|
||||
{ code: "new Date", globals: {} },
|
||||
{ code: "var eslint = require('eslint');", globals: {require: false} },
|
||||
{ code: "var fun = function({x}) {return x;};", ecmaFeatures: { destructuring: true } },
|
||||
{ code: "var fun = function([,x]) {return x;};", ecmaFeatures: { destructuring: true } },
|
||||
"function f(a) { return a.b; }",
|
||||
"var a = { \"foo\": 3 };",
|
||||
"var a = { foo: 3 };",
|
||||
"var a = { foo: 3, bar: 5 };",
|
||||
"var a = { set foo(a){}, get bar(){} };",
|
||||
"function f(a) { return arguments[0]; }",
|
||||
"function f() { }; var a = f;",
|
||||
"var a = f; function f() { };",
|
||||
"function f(){ for(var i; i; i) i; }",
|
||||
"function f(){ for(var a=0, b=1; a; b) a, b; }",
|
||||
"function f(){ for(var a in {}) a; }",
|
||||
"function f(){ switch(2) { case 1: var b = 2; b; break; default: b; break;} b; }",
|
||||
"a:;",
|
||||
{ code: "const React = require(\"react/addons\");const cx = React.addons.classSet;", globals: { require: false }, ecmaFeatures: { globalReturn: true, modules: true, blockBindings: true }},
|
||||
{ code: "var v = 1; function x() { return v; };", ecmaFeatures: { globalReturn: true }},
|
||||
{ code: "import * as y from \"./other.js\"; y();", ecmaFeatures: { modules: true }},
|
||||
{ code: "import y from \"./other.js\"; y();", ecmaFeatures: { modules: true }},
|
||||
{ code: "import {x as y} from \"./other.js\"; y();", ecmaFeatures: { modules: true }},
|
||||
{ code: "var x; export {x};", ecmaFeatures: { modules: true }},
|
||||
{ code: "var x; export {x as v};", ecmaFeatures: { modules: true }},
|
||||
{ code: "export {x} from \"./other.js\";", ecmaFeatures: { modules: true }},
|
||||
{ code: "export {x as v} from \"./other.js\";", ecmaFeatures: { modules: true }},
|
||||
{ code: "class Test { myFunction() { return true; }}", ecmaFeatures: { classes: true }},
|
||||
{ code: "class Test { get flag() { return true; }}", ecmaFeatures: { classes: true }},
|
||||
{ code: "var Test = class { myFunction() { return true; }}", ecmaFeatures: { classes: true }},
|
||||
{ code: "var doStuff; let {x: y} = {x: 1}; doStuff(y);", ecmaFeatures: { blockBindings: true, destructuring: true }},
|
||||
{ code: "function foo({x: y}) { return y; }", ecmaFeatures: { blockBindings: true, destructuring: true }},
|
||||
|
||||
// Babel-specific test-cases.
|
||||
{ code: "export x from \"./other.js\";", parser: "babel-eslint", ecmaFeatures: {modules: true} },
|
||||
{ code: "export * as x from \"./other.js\";", parser: "babel-eslint", ecmaFeatures: {modules: true} },
|
||||
],
|
||||
invalid: [
|
||||
{ code: "!function f(){}; f", errors: [{ message: "\"f\" used outside of binding context." }] },
|
||||
{ code: "var f = function foo() { }; foo(); var exports = { f: foo };", errors: [{ message: "\"foo\" used outside of binding context." }, { message: "\"foo\" used outside of binding context."}] },
|
||||
{ code: "var f = () => { x; }", ecmaFeatures: { arrowFunctions: true }, errors: [{ message: "\"x\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f(){ x; }", errors: [{ message: "\"x\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f(){ x; { var x; } }", errors: [{ message: "\"x\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f(){ { var x; } x; }", errors: [{ message: "\"x\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f() { var a; { var b = 0; } a = b; }", errors: [{ message: "\"b\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f() { try { var a = 0; } catch (e) { var b = a; } }", errors: [{ message: "\"a\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "var eslint = require('eslint');", globals: {}, errors: [{ message: "\"require\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f(a) { return a[b]; }", errors: [{ message: "\"b\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f() { return b.a; }", errors: [{ message: "\"b\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "var a = { foo: bar };", errors: [{ message: "\"bar\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "var a = { foo: foo };", errors: [{ message: "\"foo\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "var a = { bar: 7, foo: bar };", errors: [{ message: "\"bar\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "var a = arguments;", errors: [{ message: "\"arguments\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function x(){}; var a = arguments;", errors: [{ message: "\"arguments\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function z(b){}; var a = b;", errors: [{ message: "\"b\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function z(){var b;}; var a = b;", errors: [{ message: "\"b\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "function f(){ try{}catch(e){} e }", errors: [{ message: "\"e\" used outside of binding context.", type: "Identifier" }] },
|
||||
{ code: "a:b;", errors: [{ message: "\"b\" used outside of binding context.", type: "Identifier" }] },
|
||||
{
|
||||
code: "function a() { for(var b in {}) { var c = b; } c; }",
|
||||
errors: [{ message: "\"c\" used outside of binding context.", type: "Identifier" }]
|
||||
},
|
||||
{
|
||||
code: "function a() { for(var b of {}) { var c = b;} c; }",
|
||||
ecmaFeatures: { forOf: true },
|
||||
errors: [{ message: "\"c\" used outside of binding context.", type: "Identifier" }]
|
||||
}
|
||||
]
|
||||
});
|
||||
Reference in New Issue
Block a user