From bf1e344b7458c192f4d12c721c1c9d149447ccbc Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Tue, 5 Sep 2017 12:43:16 -0400 Subject: [PATCH] Chore: create report translators lazily (#9221) --- lib/linter.js | 78 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/lib/linter.js b/lib/linter.js index a52606c22fd..1a99bd171f9 100755 --- a/lib/linter.js +++ b/lib/linter.js @@ -12,7 +12,6 @@ const EventEmitter = require("events").EventEmitter, eslintScope = require("eslint-scope"), levn = require("levn"), - lodash = require("lodash"), blankScriptAST = require("../conf/blank-script.json"), defaultConfig = require("../conf/default-config-options.js"), replacements = require("../conf/replacements.json"), @@ -870,10 +869,11 @@ class Linter { } const problems = []; + const sourceCode = this.sourceCode; // parse global comments and modify config if (allowInlineConfig !== false) { - const modifyConfigResult = modifyConfigsFromComments(filename, this.sourceCode.ast, config, this); + const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this); config = modifyConfigResult.config; modifyConfigResult.problems.forEach(problem => problems.push(problem)); @@ -894,7 +894,7 @@ class Linter { getDeclaredVariables: this.getDeclaredVariables.bind(this), getFilename: () => filename, getScope: this.getScope.bind(this), - getSourceCode: () => this.sourceCode, + getSourceCode: () => sourceCode, markVariableAsUsed: this.markVariableAsUsed.bind(this), parserOptions: config.parserOptions, parserPath: config.parser, @@ -937,39 +937,53 @@ class Linter { this.rules.define(ruleId, ruleCreator); } + let reportTranslator = null; const ruleContext = Object.freeze( Object.assign( Object.create(sharedTraversalContext), { id: ruleId, options: getRuleOptions(config.rules[ruleId]), - report: lodash.flow([ - createReportTranslator({ ruleId, severity, sourceCode: this.sourceCode }), - problem => { - if (problem.fix && ruleCreator.meta && !ruleCreator.meta.fixable) { - throw new Error("Fixable rules should export a `meta.fixable` property."); - } - problems.push(problem); - - /* - * This is used to avoid breaking rules that used monkeypatch Linter, and relied on - * `linter.report` getting called with report info every time a rule reports a problem. - * To continue to support this, make sure that `context._linter.report` is called every - * time a problem is reported by a rule, even though `context._linter` is no longer a - * `Linter` instance. - * - * This should be removed in a major release after we create a better way to - * lint for unused disable comments. - * https://github.com/eslint/eslint/issues/9193 - */ - sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle - ruleId, - severity, - { loc: { start: { line: problem.line, column: problem.column - 1 } } }, - problem.message - ); + report() { + + /* + * Create a report translator lazily. + * In a vast majority of cases, any given rule reports zero errors on a given + * piece of code. Creating a translator lazily avoids the performance cost of + * creating a new translator function for each rule that usually doesn't get + * called. + * + * Using lazy report translators improves end-to-end performance by about 3% + * with Node 8.4.0. + */ + if (reportTranslator === null) { + reportTranslator = createReportTranslator({ ruleId, severity, sourceCode }); } - ]) + const problem = reportTranslator.apply(null, arguments); + + if (problem.fix && ruleCreator.meta && !ruleCreator.meta.fixable) { + throw new Error("Fixable rules should export a `meta.fixable` property."); + } + problems.push(problem); + + /* + * This is used to avoid breaking rules that used monkeypatch Linter, and relied on + * `linter.report` getting called with report info every time a rule reports a problem. + * To continue to support this, make sure that `context._linter.report` is called every + * time a problem is reported by a rule, even though `context._linter` is no longer a + * `Linter` instance. + * + * This should be removed in a major release after we create a better way to + * lint for unused disable comments. + * https://github.com/eslint/eslint/issues/9193 + */ + sharedTraversalContext._linter.report( // eslint-disable-line no-underscore-dangle + problem.ruleId, + problem.severity, + { loc: { start: { line: problem.line, column: problem.column - 1 } } }, + problem.message + ); + } } ) ); @@ -1001,7 +1015,7 @@ class Linter { const ecmaVersion = this.currentConfig.parserOptions.ecmaVersion || 5; // gather scope data that may be needed by the rules - this.scopeManager = eslintScope.analyze(this.sourceCode.ast, { + this.scopeManager = eslintScope.analyze(sourceCode.ast, { ignoreEval: true, nodejsScope: ecmaFeatures.globalReturn, impliedStrict: ecmaFeatures.impliedStrict, @@ -1011,7 +1025,7 @@ class Linter { }); // augment global scope with declared global variables - addDeclaredGlobals(this.sourceCode.ast, this.scopeManager.scopes[0], this.currentConfig, this.environments); + addDeclaredGlobals(sourceCode.ast, this.scopeManager.scopes[0], this.currentConfig, this.environments); const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter)); @@ -1021,7 +1035,7 @@ class Linter { * automatically be informed that this type of node has been found * and react accordingly. */ - this.traverser.traverse(this.sourceCode.ast, { + this.traverser.traverse(sourceCode.ast, { enter(node, parent) { node.parent = parent; eventGenerator.enterNode(node);