Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on Apr 13, 2021. It is now read-only.

Commit 5e22fac

Browse filesBrowse files
authored
Chore: AST alignment testing against Babylon (eslint#342)
1 parent 34aaa71 commit 5e22fac
Copy full SHA for 5e22fac

File tree

Expand file treeCollapse file tree

5 files changed

+269
-0
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+269
-0
lines changed

‎package.json

Copy file name to clipboardExpand all lines: package.json
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818
},
1919
"license": "BSD-2-Clause",
2020
"devDependencies": {
21+
"babel-code-frame": "^6.22.0",
22+
"babylon": "^7.0.0-beta.16",
2123
"eslint": "3.19.0",
2224
"eslint-config-eslint": "4.0.0",
2325
"eslint-plugin-node": "4.2.2",
2426
"eslint-release": "0.10.3",
27+
"glob": "^7.1.2",
2528
"jest": "20.0.4",
29+
"lodash.isplainobject": "^4.0.6",
2630
"npm-license": "0.3.3",
2731
"shelljs": "0.7.7",
2832
"shelljs-nodecli": "0.1.1",
@@ -40,6 +44,7 @@
4044
"scripts": {
4145
"test": "node Makefile.js test",
4246
"jest": "jest",
47+
"ast-alignment-tests": "jest --config={} ./tests/ast-alignment/spec.js",
4348
"lint": "node Makefile.js lint",
4449
"release": "eslint-release",
4550
"ci-release": "eslint-ci-release",

‎tests/ast-alignment/.eslintrc.yml

Copy file name to clipboard
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
env:
2+
jest: true

‎tests/ast-alignment/parse.js

Copy file name to clipboard
+90Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use strict";
2+
3+
const codeFrame = require("babel-code-frame");
4+
const parseUtils = require("./utils");
5+
6+
function createError(message, line, column) { // eslint-disable-line
7+
// Construct an error similar to the ones thrown by Babylon.
8+
const error = new SyntaxError(`${message} (${line}:${column})`);
9+
error.loc = {
10+
line,
11+
column
12+
};
13+
return error;
14+
}
15+
16+
function parseWithBabylonPluginTypescript(text) { // eslint-disable-line
17+
const babylon = require("babylon");
18+
return babylon.parse(text, {
19+
sourceType: "script",
20+
allowImportExportEverywhere: true,
21+
allowReturnOutsideFunction: true,
22+
ranges: true,
23+
plugins: [
24+
"jsx",
25+
"typescript",
26+
"doExpressions",
27+
"objectRestSpread",
28+
"decorators",
29+
"classProperties",
30+
"exportExtensions",
31+
"asyncGenerators",
32+
"functionBind",
33+
"functionSent",
34+
"dynamicImport",
35+
"numericSeparator",
36+
"estree"
37+
]
38+
});
39+
}
40+
41+
function parseWithTypeScriptESLintParser(text) { // eslint-disable-line
42+
const parser = require("../../parser");
43+
try {
44+
return parser.parse(text, {
45+
loc: true,
46+
range: true,
47+
tokens: false,
48+
comment: false,
49+
ecmaFeatures: {
50+
jsx: true
51+
}
52+
});
53+
} catch (e) {
54+
throw createError(
55+
e.message,
56+
e.lineNumber,
57+
e.column
58+
);
59+
}
60+
}
61+
62+
module.exports = function parse(text, opts) {
63+
64+
let parseFunction;
65+
66+
switch (opts.parser) {
67+
case "typescript-eslint-parser":
68+
parseFunction = parseWithTypeScriptESLintParser;
69+
break;
70+
case "babylon-plugin-typescript":
71+
parseFunction = parseWithBabylonPluginTypescript;
72+
break;
73+
default:
74+
throw new Error("Please provide a valid parser: either \"typescript-eslint-parser\" or \"babylon-plugin-typescript\"");
75+
}
76+
77+
try {
78+
return parseUtils.normalizeNodeTypes(parseFunction(text));
79+
} catch (error) {
80+
const loc = error.loc;
81+
if (loc) {
82+
error.codeFrame = codeFrame(text, loc.line, loc.column + 1, {
83+
highlightCode: true
84+
});
85+
error.message += `\n${error.codeFrame}`;
86+
}
87+
throw error;
88+
}
89+
90+
};

‎tests/ast-alignment/spec.js

Copy file name to clipboard
+106Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"use strict";
2+
3+
const fs = require("fs");
4+
const path = require("path");
5+
const glob = require("glob");
6+
const parse = require("./parse");
7+
const parseUtils = require("./utils");
8+
9+
const fixturesDirPath = path.join(__dirname, "../fixtures");
10+
const fixturePatternsToTest = [
11+
"basics/instanceof.src.js",
12+
"basics/update-expression.src.js",
13+
"basics/new-without-parens.src.js",
14+
"ecma-features/arrowFunctions/**/as*.src.js"
15+
];
16+
17+
const fixturesToTest = [];
18+
19+
fixturePatternsToTest.forEach(fixturePattern => {
20+
const matchingFixtures = glob.sync(`${fixturesDirPath}/${fixturePattern}`, {});
21+
matchingFixtures.forEach(filename => fixturesToTest.push(filename));
22+
});
23+
24+
/**
25+
* - Babylon wraps the "Program" node in an extra "File" node, normalize this for simplicity for now...
26+
* - Remove "start" and "end" values from Babylon nodes to reduce unimportant noise in diffs ("loc" data will still be in
27+
* each final AST and compared).
28+
*
29+
* @param {Object} ast raw babylon AST
30+
* @returns {Object} processed babylon AST
31+
*/
32+
function preprocessBabylonAST(ast) {
33+
return parseUtils.omitDeep(ast.program, [
34+
{
35+
key: "start",
36+
predicate(val) {
37+
// only remove the "start" number (not the "start" object within loc)
38+
return typeof val === "number";
39+
}
40+
},
41+
{
42+
key: "end",
43+
predicate(val) {
44+
// only remove the "end" number (not the "end" object within loc)
45+
return typeof val === "number";
46+
}
47+
},
48+
{
49+
key: "identifierName",
50+
predicate() {
51+
return true;
52+
}
53+
},
54+
{
55+
key: "extra",
56+
predicate() {
57+
return true;
58+
}
59+
},
60+
{
61+
key: "directives",
62+
predicate() {
63+
return true;
64+
}
65+
},
66+
{
67+
key: "innerComments",
68+
predicate() {
69+
return true;
70+
}
71+
},
72+
{
73+
key: "leadingComments",
74+
predicate() {
75+
return true;
76+
}
77+
}
78+
]);
79+
}
80+
81+
fixturesToTest.forEach(filename => {
82+
83+
const source = fs.readFileSync(filename, "utf8").replace(/\r\n/g, "\n");
84+
85+
/**
86+
* Parse with typescript-eslint-parser
87+
*/
88+
const typeScriptESLintParserAST = parse(source, {
89+
parser: "typescript-eslint-parser"
90+
});
91+
92+
/**
93+
* Parse the source with babylon typescript-plugin, and perform some extra formatting steps
94+
*/
95+
const babylonTypeScriptPluginAST = preprocessBabylonAST(parse(source, {
96+
parser: "babylon-plugin-typescript"
97+
}));
98+
99+
/**
100+
* Assert the two ASTs match
101+
*/
102+
test(`${filename}`, () => {
103+
expect(babylonTypeScriptPluginAST).toEqual(typeScriptESLintParserAST);
104+
});
105+
106+
});

‎tests/ast-alignment/utils.js

Copy file name to clipboard
+66Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use strict";
2+
3+
const isPlainObject = require("lodash.isplainobject");
4+
5+
/**
6+
* By default, pretty-format (within Jest matchers) retains the names/types of nodes from the babylon AST,
7+
* quick and dirty way to avoid that is to JSON.stringify and then JSON.parser the
8+
* ASTs before comparing them with pretty-format
9+
*
10+
* @param {Object} ast raw AST
11+
* @returns {Object} normalized AST
12+
*/
13+
function normalizeNodeTypes(ast) {
14+
return JSON.parse(JSON.stringify(ast));
15+
}
16+
17+
/**
18+
* Removes the given keys from the given AST object recursively
19+
* @param {Object} obj A JavaScript object to remove keys from
20+
* @param {Object[]} keysToOmit Names and predicate functions use to determine what keys to omit from the final object
21+
* @returns {Object} formatted object
22+
*/
23+
function omitDeep(obj, keysToOmit) {
24+
keysToOmit = keysToOmit || [];
25+
function shouldOmit(keyName, val) { // eslint-disable-line
26+
if (!keysToOmit || !keysToOmit.length) {
27+
return false;
28+
}
29+
for (const keyConfig of keysToOmit) {
30+
if (keyConfig.key !== keyName) {
31+
continue;
32+
}
33+
return keyConfig.predicate(val);
34+
}
35+
return false;
36+
}
37+
for (const key in obj) {
38+
if (!obj.hasOwnProperty(key)) {
39+
continue;
40+
}
41+
const val = obj[key];
42+
if (isPlainObject(val)) {
43+
if (shouldOmit(key, val)) {
44+
delete obj[key];
45+
break;
46+
}
47+
omitDeep(val, keysToOmit);
48+
} else if (Array.isArray(val)) {
49+
if (shouldOmit(key, val)) {
50+
delete obj[key];
51+
break;
52+
}
53+
for (const i of val) {
54+
omitDeep(i, keysToOmit);
55+
}
56+
} else if (shouldOmit(key, val)) {
57+
delete obj[key];
58+
}
59+
}
60+
return obj;
61+
}
62+
63+
module.exports = {
64+
normalizeNodeTypes,
65+
omitDeep
66+
};

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.