Skip to content

Commit 558ccba

Browse files
Chore: refactor directive comment processing (#10007)
1 parent 18e15d9 commit 558ccba

File tree

1 file changed

+98
-116
lines changed

1 file changed

+98
-116
lines changed

lib/linter.js

Lines changed: 98 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,12 @@ function parseListConfig(string) {
179179
* scope.
180180
* @param {Scope} globalScope The global scope.
181181
* @param {Object} config The existing configuration data.
182+
* @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
182183
* @param {Environments} envContext Env context
183184
* @returns {void}
184185
*/
185-
function addDeclaredGlobals(globalScope, config, envContext) {
186+
function addDeclaredGlobals(globalScope, config, commentDirectives, envContext) {
186187
const declaredGlobals = {},
187-
exportedGlobals = {},
188188
explicitGlobals = {},
189189
builtin = envContext.get("builtin");
190190

@@ -199,9 +199,8 @@ function addDeclaredGlobals(globalScope, config, envContext) {
199199
}
200200
});
201201

202-
Object.assign(exportedGlobals, config.exported);
203202
Object.assign(declaredGlobals, config.globals);
204-
Object.assign(explicitGlobals, config.astGlobals);
203+
Object.assign(explicitGlobals, commentDirectives.enabledGlobals);
205204

206205
Object.keys(declaredGlobals).forEach(name => {
207206
let variable = globalScope.set.get(name);
@@ -229,7 +228,7 @@ function addDeclaredGlobals(globalScope, config, envContext) {
229228
});
230229

231230
// mark all exported variables as such
232-
Object.keys(exportedGlobals).forEach(name => {
231+
Object.keys(commentDirectives.exportedVariables).forEach(name => {
233232
const variable = globalScope.set.get(name);
234233

235234
if (variable) {
@@ -283,108 +282,90 @@ function createDisableDirectives(type, loc, value) {
283282
* where reporting is disabled or enabled and merges them with reporting config.
284283
* @param {string} filename The file being checked.
285284
* @param {ASTNode} ast The top node of the AST.
286-
* @param {Object} config The existing configuration data.
287285
* @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
288-
* @returns {{config: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
289-
* Modified config object, along with any problems encountered while parsing config comments
286+
* @returns {{configuredRules: Object, enabledGlobals: Object, exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
287+
* A collection of the directive comments that were found, along with any problems that occurred when parsing
290288
*/
291-
function modifyConfigsFromComments(filename, ast, config, ruleMapper) {
292-
293-
const commentConfig = {
294-
exported: {},
295-
astGlobals: {},
296-
rules: {},
297-
env: {}
298-
};
299-
const commentRules = {};
289+
function getDirectiveComments(filename, ast, ruleMapper) {
290+
const configuredRules = {};
291+
const enabledGlobals = {};
292+
const exportedVariables = {};
300293
const problems = [];
301294
const disableDirectives = [];
302295

303296
ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
297+
const trimmedCommentText = comment.value.trim();
298+
const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(trimmedCommentText);
304299

305-
let value = comment.value.trim();
306-
const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
307-
308-
if (match) {
309-
value = value.slice(match.index + match[1].length);
310-
if (comment.type === "Block") {
311-
switch (match[1]) {
312-
case "exported":
313-
Object.assign(commentConfig.exported, parseBooleanConfig(value, comment));
314-
break;
315-
316-
case "globals":
317-
case "global":
318-
Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment));
319-
break;
320-
321-
case "eslint-disable":
322-
[].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value));
323-
break;
324-
325-
case "eslint-disable-line":
326-
if (comment.loc.start.line === comment.loc.end.line) {
327-
[].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
328-
}
329-
break;
330-
331-
case "eslint-disable-next-line":
332-
if (comment.loc.start.line === comment.loc.end.line) {
333-
[].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
334-
}
335-
break;
336-
337-
case "eslint-enable":
338-
[].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value));
339-
break;
340-
341-
case "eslint": {
342-
const parseResult = parseJsonConfig(value, comment.loc);
343-
344-
if (parseResult.success) {
345-
Object.keys(parseResult.config).forEach(name => {
346-
const ruleValue = parseResult.config[name];
347-
348-
try {
349-
validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
350-
} catch (err) {
351-
problems.push({
352-
ruleId: name,
353-
severity: 2,
354-
source: null,
355-
message: err.message,
356-
line: comment.loc.start.line,
357-
column: comment.loc.start.column + 1,
358-
endLine: comment.loc.end.line,
359-
endColumn: comment.loc.end.column + 1,
360-
nodeType: null
361-
});
362-
}
363-
commentRules[name] = ruleValue;
364-
});
365-
} else {
366-
problems.push(parseResult.error);
367-
}
300+
if (!match) {
301+
return;
302+
}
368303

369-
break;
304+
const directiveValue = trimmedCommentText.slice(match.index + match[1].length);
305+
306+
if (/^eslint-disable-(next-)?line$/.test(match[1]) && comment.loc.start.line === comment.loc.end.line) {
307+
const directiveType = match[1].slice("eslint-".length);
308+
309+
[].push.apply(disableDirectives, createDisableDirectives(directiveType, comment.loc.start, directiveValue));
310+
} else if (comment.type === "Block") {
311+
switch (match[1]) {
312+
case "exported":
313+
Object.assign(exportedVariables, parseBooleanConfig(directiveValue, comment));
314+
break;
315+
316+
case "globals":
317+
case "global":
318+
Object.assign(enabledGlobals, parseBooleanConfig(directiveValue, comment));
319+
break;
320+
321+
case "eslint-disable":
322+
[].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, directiveValue));
323+
break;
324+
325+
case "eslint-enable":
326+
[].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, directiveValue));
327+
break;
328+
329+
case "eslint": {
330+
const parseResult = parseJsonConfig(directiveValue, comment.loc);
331+
332+
if (parseResult.success) {
333+
Object.keys(parseResult.config).forEach(name => {
334+
const ruleValue = parseResult.config[name];
335+
336+
try {
337+
validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
338+
} catch (err) {
339+
problems.push({
340+
ruleId: name,
341+
severity: 2,
342+
source: null,
343+
message: err.message,
344+
line: comment.loc.start.line,
345+
column: comment.loc.start.column + 1,
346+
endLine: comment.loc.end.line,
347+
endColumn: comment.loc.end.column + 1,
348+
nodeType: null
349+
});
350+
}
351+
configuredRules[name] = ruleValue;
352+
});
353+
} else {
354+
problems.push(parseResult.error);
370355
}
371356

372-
// no default
373-
}
374-
} else { // comment.type === "Line"
375-
if (match[1] === "eslint-disable-line") {
376-
[].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value));
377-
} else if (match[1] === "eslint-disable-next-line") {
378-
[].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value));
357+
break;
379358
}
359+
360+
// no default
380361
}
381362
}
382363
});
383364

384-
Object.assign(commentConfig.rules, commentRules);
385-
386365
return {
387-
config: ConfigOps.merge(config, commentConfig),
366+
configuredRules,
367+
enabledGlobals,
368+
exportedVariables,
388369
problems,
389370
disableDirectives
390371
};
@@ -775,10 +756,11 @@ module.exports = class Linter {
775756
// evaluate arguments
776757
if (typeof filenameOrOptions === "object") {
777758
providedFilename = filenameOrOptions.filename;
778-
allowInlineConfig = filenameOrOptions.allowInlineConfig;
759+
allowInlineConfig = filenameOrOptions.allowInlineConfig !== false;
779760
reportUnusedDisableDirectives = filenameOrOptions.reportUnusedDisableDirectives;
780761
} else {
781762
providedFilename = filenameOrOptions;
763+
allowInlineConfig = true;
782764
}
783765

784766
if (typeof textOrSourceCode === "string") {
@@ -861,24 +843,23 @@ module.exports = class Linter {
861843
}
862844
}
863845

864-
const problems = [];
865846
const sourceCode = lastSourceCodes.get(this);
866-
let disableDirectives;
847+
const commentDirectives = allowInlineConfig
848+
? getDirectiveComments(filename, sourceCode.ast, ruleId => this.rules.get(ruleId))
849+
: { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
867850

868-
// parse global comments and modify config
869-
if (allowInlineConfig !== false) {
870-
const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, ruleId => this.rules.get(ruleId));
851+
const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
871852

872-
config = modifyConfigResult.config;
873-
modifyConfigResult.problems.forEach(problem => problems.push(problem));
874-
disableDirectives = modifyConfigResult.disableDirectives;
875-
} else {
876-
disableDirectives = [];
877-
}
853+
// augment global scope with declared global variables
854+
addDeclaredGlobals(
855+
sourceCode.scopeManager.scopes[0],
856+
config,
857+
{ exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals },
858+
this.environments
859+
);
878860

879861
const emitter = createEmitter();
880862
const traverser = new Traverser();
881-
const scopeManager = sourceCode.scopeManager;
882863

883864
/*
884865
* Create a frozen object with the ruleContext properties and methods that are shared by all rules.
@@ -890,11 +871,11 @@ module.exports = class Linter {
890871
Object.create(BASE_TRAVERSAL_CONTEXT),
891872
{
892873
getAncestors: () => traverser.parents(),
893-
getDeclaredVariables: scopeManager.getDeclaredVariables.bind(scopeManager),
874+
getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
894875
getFilename: () => filename,
895-
getScope: () => getScope(scopeManager, traverser.current(), config.parserOptions.ecmaVersion),
876+
getScope: () => getScope(sourceCode.scopeManager, traverser.current(), config.parserOptions.ecmaVersion),
896877
getSourceCode: () => sourceCode,
897-
markVariableAsUsed: name => markVariableAsUsed(scopeManager, traverser.current(), config.parserOptions, name),
878+
markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, traverser.current(), config.parserOptions, name),
898879
parserOptions: config.parserOptions,
899880
parserPath: config.parser,
900881
parserServices: sourceCode.parserServices,
@@ -916,9 +897,11 @@ module.exports = class Linter {
916897
)
917898
);
918899

900+
const lintingProblems = [];
901+
919902
// enable appropriate rules
920-
Object.keys(config.rules).forEach(ruleId => {
921-
const severity = ConfigOps.getRuleSeverity(config.rules[ruleId]);
903+
Object.keys(configuredRules).forEach(ruleId => {
904+
const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
922905

923906
if (severity === 0) {
924907
return;
@@ -932,7 +915,7 @@ module.exports = class Linter {
932915
Object.create(sharedTraversalContext),
933916
{
934917
id: ruleId,
935-
options: getRuleOptions(config.rules[ruleId]),
918+
options: getRuleOptions(configuredRules[ruleId]),
936919
report() {
937920

938921
/*
@@ -953,7 +936,7 @@ module.exports = class Linter {
953936
if (problem.fix && rule.meta && !rule.meta.fixable) {
954937
throw new Error("Fixable rules should export a `meta.fixable` property.");
955938
}
956-
problems.push(problem);
939+
lintingProblems.push(problem);
957940

958941
/*
959942
* This is used to avoid breaking rules that used monkeypatch Linter, and relied on
@@ -995,9 +978,6 @@ module.exports = class Linter {
995978
}
996979
});
997980

998-
// augment global scope with declared global variables
999-
addDeclaredGlobals(scopeManager.scopes[0], config, this.environments);
1000-
1001981
const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
1002982

1003983
/*
@@ -1018,8 +998,10 @@ module.exports = class Linter {
1018998
});
1019999

10201000
return applyDisableDirectives({
1021-
directives: disableDirectives,
1022-
problems: problems.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
1001+
directives: commentDirectives.disableDirectives,
1002+
problems: lintingProblems
1003+
.concat(commentDirectives.problems)
1004+
.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
10231005
reportUnusedDisableDirectives
10241006
});
10251007
}

0 commit comments

Comments
 (0)