Skip to content

Commit cbbe420

Browse files
authored
New: Support enhanced parsers (fixes #6974) (#6975)
1 parent 644d25b commit cbbe420

File tree

5 files changed

+123
-5
lines changed

5 files changed

+123
-5
lines changed

lib/eslint.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ const assert = require("assert"),
2828
rules = require("./rules"),
2929
timing = require("./timing");
3030

31+
//------------------------------------------------------------------------------
32+
// Typedefs
33+
//------------------------------------------------------------------------------
34+
35+
/**
36+
* The result of a parsing operation from parseForESLint()
37+
* @typedef {Object} CustomParseResult
38+
* @property {ASTNode} ast The ESTree AST Program node.
39+
* @property {Object} services An object containing additional services related
40+
* to the parser.
41+
*/
42+
3143
//------------------------------------------------------------------------------
3244
// Helpers
3345
//------------------------------------------------------------------------------
@@ -598,7 +610,8 @@ module.exports = (function() {
598610
* @param {string} text The text to parse.
599611
* @param {Object} config The ESLint configuration object.
600612
* @param {string} filePath The path to the file being parsed.
601-
* @returns {ASTNode} The AST if successful or null if not.
613+
* @returns {ASTNode|CustomParseResult} The AST or parse result if successful,
614+
* or null if not.
602615
* @private
603616
*/
604617
function parse(text, config, filePath) {
@@ -642,7 +655,11 @@ module.exports = (function() {
642655
* problem that ESLint identified just like any other.
643656
*/
644657
try {
645-
return parser.parse(text, parserOptions);
658+
if (typeof parser.parseForESLint === "function") {
659+
return parser.parseForESLint(text, parserOptions);
660+
} else {
661+
return parser.parse(text, parserOptions);
662+
}
646663
} catch (ex) {
647664

648665
// If the message includes a leading line number, strip it:
@@ -738,6 +755,7 @@ module.exports = (function() {
738755
api.verify = function(textOrSourceCode, config, filenameOrOptions, saveState) {
739756
const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
740757
let ast,
758+
parseResult,
741759
shebang,
742760
allowInlineConfig;
743761

@@ -778,7 +796,7 @@ module.exports = (function() {
778796
return messages;
779797
}
780798

781-
ast = parse(
799+
parseResult = parse(
782800
stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, function(match, captured) {
783801
shebang = captured;
784802
return `//${captured}`;
@@ -787,6 +805,14 @@ module.exports = (function() {
787805
currentFilename
788806
);
789807

808+
// if this result is from a parseForESLint() method, normalize
809+
if (parseResult && parseResult.ast) {
810+
ast = parseResult.ast;
811+
} else {
812+
ast = parseResult;
813+
parseResult = null;
814+
}
815+
790816
if (ast) {
791817
sourceCode = new SourceCode(text, ast);
792818
}
@@ -832,7 +858,10 @@ module.exports = (function() {
832858
try {
833859
const ruleContext = new RuleContext(
834860
key, api, severity, options,
835-
config.settings, config.parserOptions, config.parser, ruleCreator.meta);
861+
config.settings, config.parserOptions, config.parser,
862+
ruleCreator.meta,
863+
(parseResult && parseResult.services ? parseResult.services : {})
864+
);
836865

837866
const rule = ruleCreator.create ? ruleCreator.create(ruleContext) :
838867
ruleCreator(ruleContext);

lib/rule-context.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ const PASSTHROUGHS = [
7171
* @param {Object} parserOptions The parserOptions settings passed from the config file.
7272
* @param {Object} parserPath The parser setting passed from the config file.
7373
* @param {Object} meta The metadata of the rule
74+
* @param {Object} parserServices The parser services for the rule.
7475
*/
75-
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) {
76+
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
7677

7778
// public.
7879
this.id = ruleId;
@@ -82,6 +83,9 @@ function RuleContext(ruleId, eslint, severity, options, settings, parserOptions,
8283
this.parserPath = parserPath;
8384
this.meta = meta;
8485

86+
// create a separate copy and freeze it (it's not nice to freeze other people's objects)
87+
this.parserServices = Object.freeze(Object.assign({}, parserServices));
88+
8589
// private.
8690
this.eslint = eslint;
8791
this.severity = severity;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
var espree = require("espree");
2+
3+
exports.parseForESLint = function(code, options) {
4+
return {
5+
ast: espree.parse(code, options),
6+
services: {
7+
test: {
8+
getMessage() {
9+
return "Hi!";
10+
}
11+
}
12+
}
13+
};
14+
};
15+
16+
exports.parse = function() {
17+
throw new Error("Use parseForESLint() instead.");
18+
};

tests/lib/eslint.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,41 @@ describe("eslint", function() {
12681268

12691269
eslint.verify("0", config, filename);
12701270
});
1271+
1272+
it("should use parseForESLint() in custom parser when custom parser is specified", function() {
1273+
1274+
const alternateParser = path.resolve(__dirname, "../fixtures/parsers/enhanced-parser.js");
1275+
1276+
eslint.reset();
1277+
1278+
const config = { rules: {}, parser: alternateParser };
1279+
const messages = eslint.verify("0", config, filename);
1280+
1281+
assert.equal(messages.length, 0);
1282+
});
1283+
1284+
it("should expose parser services when using parseForESLint() and services are specified", function() {
1285+
1286+
const alternateParser = path.resolve(__dirname, "../fixtures/parsers/enhanced-parser.js");
1287+
1288+
eslint.reset();
1289+
eslint.defineRule("test-service-rule", function(context) {
1290+
return {
1291+
Literal(node) {
1292+
context.report({
1293+
node,
1294+
message: context.parserServices.test.getMessage()
1295+
});
1296+
}
1297+
};
1298+
});
1299+
1300+
const config = { rules: { "test-service-rule": 2 }, parser: alternateParser };
1301+
const messages = eslint.verify("0", config, filename);
1302+
1303+
assert.equal(messages.length, 1);
1304+
assert.equal(messages[0].message, "Hi!");
1305+
});
12711306
}
12721307

12731308
it("should pass parser as parserPath to all rules when default parser is used", function() {

tests/lib/rule-context.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//------------------------------------------------------------------------------
1111

1212
const sinon = require("sinon"),
13+
assert = require("chai").assert,
1314
leche = require("leche"),
1415
realESLint = require("../../lib/eslint"),
1516
RuleContext = require("../../lib/rule-context");
@@ -95,4 +96,35 @@ describe("RuleContext", function() {
9596
});
9697
});
9798

99+
describe("parserServices", function() {
100+
101+
it("should pass through parserServices properties to context", function() {
102+
const services = {
103+
test: {}
104+
};
105+
const ruleContext = new RuleContext("fake-rule", {}, 2, {}, {}, {}, "espree", {}, services);
106+
107+
assert.equal(ruleContext.parserServices.test, services.test);
108+
});
109+
110+
it("should copy parserServices properties to a new object", function() {
111+
const services = {
112+
test: {}
113+
};
114+
const ruleContext = new RuleContext("fake-rule", {}, 2, {}, {}, {}, "espree", {}, services);
115+
116+
assert.notEqual(ruleContext.parserServices, services);
117+
});
118+
119+
it("should make context.parserServices a frozen object", function() {
120+
const services = {
121+
test: {}
122+
};
123+
const ruleContext = new RuleContext("fake-rule", {}, 2, {}, {}, {}, "espree", {}, services);
124+
125+
assert.ok(Object.isFrozen(ruleContext.parserServices));
126+
});
127+
128+
});
129+
98130
});

0 commit comments

Comments
 (0)