Skip to content

Commit

Permalink
refactor: Allow optional methods for languages (eslint#18604)
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas authored Jun 19, 2024
1 parent f9e95d2 commit 0a13539
Showing 1 changed file with 143 additions and 137 deletions.
280 changes: 143 additions & 137 deletions lib/linter/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -595,22 +595,24 @@ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) {
const disableDirectives = [];
const problems = [];

const {
directives: directivesSources,
problems: directivesProblems
} = sourceCode.getDisableDirectives();

problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({
...directiveProblem,
language
})));

directivesSources.forEach(directive => {
const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);

disableDirectives.push(...directives);
problems.push(...directiveProblems);
});
if (sourceCode.getDisableDirectives) {
const {
directives: directivesSources,
problems: directivesProblems
} = sourceCode.getDisableDirectives();

problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({
...directiveProblem,
language
})));

directivesSources.forEach(directive => {
const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);

disableDirectives.push(...directives);
problems.push(...directiveProblems);
});
}

return {
problems,
Expand Down Expand Up @@ -1131,7 +1133,7 @@ function runRules(
const eventGenerator = new NodeEventGenerator(emitter, {
visitorKeys: sourceCode.visitorKeys,
fallback: Traverser.getKeys,
matchClass: language.matchesSelectorClass,
matchClass: language.matchesSelectorClass ?? (() => false),
nodeTypeKey: language.nodeTypeKey
});

Expand Down Expand Up @@ -1706,7 +1708,7 @@ class Linter {
* this is primarily about adding variables into the global scope
* to account for ecmaVersion and configured globals.
*/
sourceCode.applyLanguageOptions(languageOptions);
sourceCode.applyLanguageOptions?.(languageOptions);

const mergedInlineConfig = {
rules: {}
Expand All @@ -1723,147 +1725,151 @@ class Linter {

// if inline config should warn then add the warnings
if (options.warnInlineConfig) {
sourceCode.getInlineConfigNodes().forEach(node => {
inlineConfigProblems.push(createLintingProblem({
ruleId: null,
message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
loc: node.loc,
severity: 1,
language: config.language
}));
if (sourceCode.getInlineConfigNodes) {
sourceCode.getInlineConfigNodes().forEach(node => {
inlineConfigProblems.push(createLintingProblem({
ruleId: null,
message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
loc: node.loc,
severity: 1,
language: config.language
}));

});
});
}
} else {
const inlineConfigResult = sourceCode.applyInlineConfig();

inlineConfigProblems.push(
...inlineConfigResult.problems
.map(problem => createLintingProblem({ ...problem, language: config.language }))
.map(problem => {
problem.fatal = true;
return problem;
})
);
const inlineConfigResult = sourceCode.applyInlineConfig?.();

if (inlineConfigResult) {
inlineConfigProblems.push(
...inlineConfigResult.problems
.map(problem => createLintingProblem({ ...problem, language: config.language }))
.map(problem => {
problem.fatal = true;
return problem;
})
);

// next we need to verify information about the specified rules
const ruleValidator = new RuleValidator();

for (const { config: inlineConfig, node } of inlineConfigResult.configs) {

Object.keys(inlineConfig.rules).forEach(ruleId => {
const rule = getRuleFromConfig(ruleId, config);
const ruleValue = inlineConfig.rules[ruleId];

if (!rule) {
inlineConfigProblems.push(createLintingProblem({
ruleId,
loc: node.loc,
language: config.language
}));
return;
}

// next we need to verify information about the specified rules
const ruleValidator = new RuleValidator();
if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
inlineConfigProblems.push(createLintingProblem({
message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
loc: node.loc,
language: config.language
}));
return;
}

for (const { config: inlineConfig, node } of inlineConfigResult.configs) {
try {

Object.keys(inlineConfig.rules).forEach(ruleId => {
const rule = getRuleFromConfig(ruleId, config);
const ruleValue = inlineConfig.rules[ruleId];
let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];

if (!rule) {
inlineConfigProblems.push(createLintingProblem({
ruleId,
loc: node.loc,
language: config.language
}));
return;
}
assertIsRuleSeverity(ruleId, ruleOptions[0]);

if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
inlineConfigProblems.push(createLintingProblem({
message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
loc: node.loc,
language: config.language
}));
return;
}
/*
* If the rule was already configured, inline rule configuration that
* only has severity should retain options from the config and just override the severity.
*
* Example:
*
* {
* rules: {
* curly: ["error", "multi"]
* }
* }
*
* /* eslint curly: ["warn"] * /
*
* Results in:
*
* curly: ["warn", "multi"]
*/

try {
let shouldValidateOptions = true;

let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
if (

assertIsRuleSeverity(ruleId, ruleOptions[0]);
/*
* If inline config for the rule has only severity
*/
ruleOptions.length === 1 &&

/*
* If the rule was already configured, inline rule configuration that
* only has severity should retain options from the config and just override the severity.
*
* Example:
*
* {
* rules: {
* curly: ["error", "multi"]
* }
* }
*
* /* eslint curly: ["warn"] * /
*
* Results in:
*
* curly: ["warn", "multi"]
*/
/*
* And the rule was already configured
*/
config.rules && Object.hasOwn(config.rules, ruleId)
) {

let shouldValidateOptions = true;
/*
* Then use severity from the inline config and options from the provided config
*/
ruleOptions = [
ruleOptions[0], // severity from the inline config
...config.rules[ruleId].slice(1) // options from the provided config
];

if (
// if the rule was enabled, the options have already been validated
if (config.rules[ruleId][0] > 0) {
shouldValidateOptions = false;
}
}

/*
* If inline config for the rule has only severity
*/
ruleOptions.length === 1 &&
if (shouldValidateOptions) {
ruleValidator.validate({
plugins: config.plugins,
rules: {
[ruleId]: ruleOptions
}
});
}

/*
* And the rule was already configured
*/
config.rules && Object.hasOwn(config.rules, ruleId)
) {
mergedInlineConfig.rules[ruleId] = ruleOptions;
} catch (err) {

/*
* Then use severity from the inline config and options from the provided config
* If the rule has invalid `meta.schema`, throw the error because
* this is not an invalid inline configuration but an invalid rule.
*/
ruleOptions = [
ruleOptions[0], // severity from the inline config
...config.rules[ruleId].slice(1) // options from the provided config
];

// if the rule was enabled, the options have already been validated
if (config.rules[ruleId][0] > 0) {
shouldValidateOptions = false;
if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
throw err;
}
}

if (shouldValidateOptions) {
ruleValidator.validate({
plugins: config.plugins,
rules: {
[ruleId]: ruleOptions
}
});
}

mergedInlineConfig.rules[ruleId] = ruleOptions;
} catch (err) {

/*
* If the rule has invalid `meta.schema`, throw the error because
* this is not an invalid inline configuration but an invalid rule.
*/
if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
throw err;
}
let baseMessage = err.message.slice(
err.message.startsWith("Key \"rules\":")
? err.message.indexOf(":", 12) + 1
: err.message.indexOf(":") + 1
).trim();

let baseMessage = err.message.slice(
err.message.startsWith("Key \"rules\":")
? err.message.indexOf(":", 12) + 1
: err.message.indexOf(":") + 1
).trim();
if (err.messageTemplate) {
baseMessage += ` You passed "${ruleValue}".`;
}

if (err.messageTemplate) {
baseMessage += ` You passed "${ruleValue}".`;
inlineConfigProblems.push(createLintingProblem({
ruleId,
message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
loc: node.loc,
language: config.language
}));
}

inlineConfigProblems.push(createLintingProblem({
ruleId,
message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
loc: node.loc,
language: config.language
}));
}
});
});
}
}
}
}
Expand All @@ -1880,7 +1886,7 @@ class Linter {

let lintingProblems;

sourceCode.finalize();
sourceCode.finalize?.();

try {
lintingProblems = runRules(
Expand Down

0 comments on commit 0a13539

Please sign in to comment.