From 6556b06f231b1bac297dba5f6aefe1bf324dba4a Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Mon, 9 Mar 2015 02:45:56 +1100 Subject: [PATCH] Merge branch 'master' of github.com:babel/babel-eslint --- .../babel-eslint-parser/acorn-to-esprima.js | 42 ++--- eslint/babel-eslint-parser/index.js | 13 +- eslint/babel-eslint-parser/package.json | 10 +- .../babel-eslint-parser/test/babel-eslint.js | 90 +++++++++++ .../test/non-regression.js | 145 ++++++++++++++++++ 5 files changed, 279 insertions(+), 21 deletions(-) create mode 100644 eslint/babel-eslint-parser/test/babel-eslint.js create mode 100644 eslint/babel-eslint-parser/test/non-regression.js diff --git a/eslint/babel-eslint-parser/acorn-to-esprima.js b/eslint/babel-eslint-parser/acorn-to-esprima.js index ad39a897a4..66e1993c86 100644 --- a/eslint/babel-eslint-parser/acorn-to-esprima.js +++ b/eslint/babel-eslint-parser/acorn-to-esprima.js @@ -7,9 +7,24 @@ exports.toToken = function (token) { if (type === tokTypes.name) { token.type = "Identifier"; - } else if (type === tokTypes.semi || type === tokTypes.comma || type === tokTypes.parenL || type === tokTypes.parenR || type === tokTypes.braceL || type === tokTypes.braceR) { + } else if (type === tokTypes.semi || type === tokTypes.comma || type === tokTypes.parenL || type === tokTypes.parenR || type === tokTypes.braceL || type === tokTypes.braceR || type === tokTypes.slash || type === tokTypes.dot || type.isAssign) { token.type = "Punctuator"; - token.value = type.type; + if (!token.value) { + token.value = type.type; + } + } else if (type === tokTypes.jsxTagStart) { + token.type = "Punctuator"; + token.value = "<"; + } else if (type === tokTypes.jsxTagEnd) { + token.type = "Punctuator"; + token.value = ">"; + } else if (type === tokTypes.jsxName) { + token.type = "JSXIdentifier"; + } else if (type.keyword) { + token.type = "Keyword"; + } else if (type === tokTypes.num) { + token.type = "Numeric"; + token.value = String(token.value); } return token; @@ -50,7 +65,12 @@ var astTransformVisitor = { } // classes - + + if (t.isClassDeclaration(node) || t.isClassExpression(node)) { + node.name = node.id; + delete node.id; + } + if (t.isReferencedIdentifier(node, parent, { name: "super" })) { return t.inherits(t.thisExpression(), node); } @@ -60,22 +80,6 @@ var astTransformVisitor = { this.remove(); } - // JSX - - if (t.isJSXIdentifier(node)) { - if (node.name === "this" && t.isReferenced(node, parent)) { - return t.inherits(t.thisExpression(), node); - } else if (!t.isJSXAttribute(parent) && !isCompatTag(node.name)) { - node.type = "Identifier"; - } else { - // just ignore this node as it'd be something like
or an attribute name - } - } - - if (t.isJSXMemberExpression(node)) { - node.type = "MemberExpression"; - } - // functions if (t.isFunction(node)) { diff --git a/eslint/babel-eslint-parser/index.js b/eslint/babel-eslint-parser/index.js index ca364bad9c..3def57ec38 100644 --- a/eslint/babel-eslint-parser/index.js +++ b/eslint/babel-eslint-parser/index.js @@ -21,7 +21,7 @@ function monkeypatch() { var eslintLoc; try { - eslintLoc = require.resolve("eslint"); + eslintLoc = Module._resolveFilename("eslint", module.parent); } catch (err) { throw new ReferenceError("couldn't resolve eslint"); } @@ -43,6 +43,17 @@ function monkeypatch() { opts.sourceType = "module"; return analyze.call(this, ast, opts) }; + + var eslint = require(eslintLoc); + var getScope = eslint.linter.getScope; + eslint.linter.getScope = function () { + var scope = getScope.apply(this, arguments); + if (scope.type === "global" && !scope.__patchedWithModuleVariables) { + scope.__patchedWithModuleVariables = true; + scope.variables.push.apply(scope.variables, scope.childScopes[0].variables); + } + return scope; + }; } exports.parse = function (code) { diff --git a/eslint/babel-eslint-parser/package.json b/eslint/babel-eslint-parser/package.json index e0a103db37..c064dadd48 100644 --- a/eslint/babel-eslint-parser/package.json +++ b/eslint/babel-eslint-parser/package.json @@ -16,5 +16,13 @@ "bugs": { "url": "https://github.com/babel/babel-eslint/issues" }, - "homepage": "https://github.com/babel/babel-eslint" + "homepage": "https://github.com/babel/babel-eslint", + "devDependencies": { + "eslint": "^0.15.1", + "espree": "^1.10.0", + "mocha": "^2.1.0" + }, + "scripts": { + "test": "mocha" + } } diff --git a/eslint/babel-eslint-parser/test/babel-eslint.js b/eslint/babel-eslint-parser/test/babel-eslint.js new file mode 100644 index 0000000000..f8365c1844 --- /dev/null +++ b/eslint/babel-eslint-parser/test/babel-eslint.js @@ -0,0 +1,90 @@ +var util = require("util"); +var espree = require("espree"); +var babelEslint = require(".."); + + +function assertSameAST(a, b, path) { + if (!path) { + path = []; + } + + function error(text) { + throw new Error("At " + path.join(".") + ": " + text + ":\n" + util.inspect(a) + "\n" + util.inspect(b)); + } + + var typeA = a === null ? "null" : typeof a; + var typeB = b === null ? "null" : typeof b; + if (typeA !== typeB) { + error("have not the same type (" + typeA + " !== " + typeB + ")"); + } else if (typeA === "object") { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + keysA.sort(); + keysB.sort(); + while (true) { + var keyA = keysA.shift(); + + // Exception: ignore "end" and "start" outside "loc" properties + if ((keyA === "end" || keyA === "start") && path[path.length - 1] !== "loc") continue; + // Exception: ignore root "comments" property + if (keyA === "comments" && path.length === 0) continue; + + var keyB = keysB.shift(); + + if (keyA === undefined && keyB === undefined) break; + if (keyA === undefined || keyA > keyB) error("first does not have key \"" + keyB + "\""); + if (keyB === undefined || keyA < keyB) error("second does not have key \"" + keyA + "\""); + path.push(keyA); + assertSameAST(a[keyA], b[keyB], path); + path.pop(); + } + } else if (a !== b) { + error("are different (" + JSON.stringify(a) + " !== " + JSON.stringify(b) + ")"); + } +} + +function parseAndAssertSame(code) { + var esAST = espree.parse(code, { + ecmaFeatures: { + classes: true, + jsx: true + }, + tokens: true, + loc: true, + range: true + }); + var acornAST = babelEslint.parse(code); + assertSameAST(acornAST, esAST); +} + +describe("acorn-to-esprima", function () { + + it("simple expression", function () { + parseAndAssertSame("a = 1"); + }); + + it("class declaration", function () { + parseAndAssertSame("class Foo {}"); + }); + + it("class expression", function () { + parseAndAssertSame("var a = class Foo {}"); + }); + + it("jsx expression", function () { + parseAndAssertSame(""); + }); + + it("jsx expression with 'this' as identifier", function () { + parseAndAssertSame(""); + }); + + it("jsx expression with a dynamic attribute", function () { + parseAndAssertSame(""); + }); + + it("jsx expression with a member expression as identifier", function () { + parseAndAssertSame(""); + }); + +}); diff --git a/eslint/babel-eslint-parser/test/non-regression.js b/eslint/babel-eslint-parser/test/non-regression.js new file mode 100644 index 0000000000..68f5113109 --- /dev/null +++ b/eslint/babel-eslint-parser/test/non-regression.js @@ -0,0 +1,145 @@ +/*eslint-env mocha*/ +"use strict"; +var eslint = require("eslint"); + +function verifyAndAssertMessages(code, rules, expectedMessages) { + var messages = eslint.linter.verify( + code, + { + parser: require.resolve(".."), + rules: rules, + env: { + node: true + } + } + ); + + if (messages.length !== expectedMessages.length) { + throw new Error("Expected " + expectedMessages.length + " message(s), got " + messages.length); + } + + messages.forEach(function (message, i) { + var formatedMessage = message.line + ":" + message.column + " " + message.message + (message.ruleId ? " " + message.ruleId : ""); + if (formatedMessage !== expectedMessages[i]) { + throw new Error("Message " + i + " does not match:\nExpected: " + expectedMessages[i] + "\nActual: " + formatedMessage); + } + }); +} + +describe("verify", function () { + + it("arrow function support (issue #1)", function () { + verifyAndAssertMessages( + "describe('stuff', () => {});", + {}, + [] + ); + }); + + it("EOL validation (issue #2)", function () { + verifyAndAssertMessages( + "module.exports = \"something\";", + { "eol-last": 1, "semi": 1 }, + [ "1:1 Newline required at end of file but not found. eol-last" ] + ); + }); + + it("Readable error messages (issue #3)", function () { + verifyAndAssertMessages( + "{ , res }", + {}, + [ "1:2 Unexpected token" ] + ); + }); + + it("Unused vars in JSX (issue #5)", function () { + verifyAndAssertMessages( + "var App = require('./App');\n" + + "module.exports = ;", + { "no-unused-vars": 1 }, + [] + ); + }); + + it("Modules support (issue #5)", function () { + verifyAndAssertMessages( + "import Foo from 'foo';\n" + + "export default Foo;", + { }, + [] + ); + }); + + it("Rest parameters (issue #7)", function () { + verifyAndAssertMessages( + "function foo(...args) { return args; }", + { "no-undef": 1 }, + [] + ); + }); + + it("Exported classes should be used (issue #8)", function () { + verifyAndAssertMessages( + "class Foo {} module.exports = Foo;", + { "no-unused-vars": 1 }, + [] + ); + }); + + it("super keyword in class (issue #10)", function () { + verifyAndAssertMessages( + "class Foo { constructor() { super() } }", + { "no-undef": 1 }, + [] + ); + }); + + it("Rest parameter in destructuring assignment (issue #11)", function () { + verifyAndAssertMessages( + "const [a, ...rest] = ['1', '2', '3']; module.exports = rest;", + { "no-undef": 1 }, + [] + ); + }); + + it("JSX attribute names marked as variables (issue #12)", function () { + verifyAndAssertMessages( + "module.exports =
", + { "no-undef": 1 }, + [] + ); + }); + + it("Variables in JSX should be used (issues #15, #17, #21, #29)", function () { + verifyAndAssertMessages( + "import App from './App'; export default ();", + { "no-unused-vars": 1, "no-undef": 1 }, + [] + ); + }); + + it("Multiple destructured assignment with compound properties (issue #16)", function () { + verifyAndAssertMessages( + "module.exports = { ...a.a, ...a.b };", + { "no-dupe-keys": 1 }, + [] + ); + }); + + it("Arrow function with non-block bodies (issue #20)", function () { + verifyAndAssertMessages( + "\"use strict\"; () => 1", + { "strict": 1 }, + [] + ); + }); + + it("await keyword (issue #22)", function () { + verifyAndAssertMessages( + "async function foo() { await bar(); }", + { "no-unused-expressions": 1 }, + [] + ); + }); + +});