eslint
Advanced tools
Comparing version 8.29.0 to 8.51.0
@@ -96,2 +96,10 @@ #!/usr/bin/env node | ||
/** | ||
* Tracks error messages that are shown to the user so we only ever show the | ||
* same message once. | ||
* @type {Set<string>} | ||
*/ | ||
const displayedErrors = new Set(); | ||
/** | ||
* Catch and report unexpected error. | ||
@@ -105,5 +113,3 @@ * @param {any} error The thrown error object. | ||
const { version } = require("../package.json"); | ||
const message = getErrorMessage(error); | ||
console.error(` | ||
const message = ` | ||
Oops! Something went wrong! :( | ||
@@ -113,3 +119,8 @@ | ||
${message}`); | ||
${getErrorMessage(error)}`; | ||
if (!displayedErrors.has(message)) { | ||
console.error(message); | ||
displayedErrors.add(message); | ||
} | ||
} | ||
@@ -116,0 +127,0 @@ |
@@ -131,3 +131,7 @@ /** | ||
const es2024 = { | ||
...es2023 | ||
}; | ||
//----------------------------------------------------------------------------- | ||
@@ -149,3 +153,4 @@ // Exports | ||
es2022, | ||
es2023 | ||
es2023, | ||
es2024 | ||
}; |
@@ -9,3 +9,3 @@ { | ||
"name": "Deprecated", | ||
"description": "These rules have been deprecated in accordance with the <a href=\"/docs/user-guide/rule-deprecation\">deprecation policy</a>, and replaced by newer rules:", | ||
"description": "These rules have been deprecated in accordance with the <a href=\"{{ '/use/rule-deprecation' | url }}\">deprecation policy</a>, and replaced by newer rules:", | ||
"rules": [] | ||
@@ -15,3 +15,3 @@ }, | ||
"name": "Removed", | ||
"description": "These rules from older versions of ESLint (before the <a href=\"/docs/user-guide/rule-deprecation\">deprecation policy</a> existed) have been replaced by newer rules:", | ||
"description": "These rules from older versions of ESLint (before the <a href=\"{{ '/use/rule-deprecation' | url }}\">deprecation policy</a> existed) have been replaced by newer rules:", | ||
"rules": [ | ||
@@ -18,0 +18,0 @@ { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, |
@@ -161,3 +161,13 @@ /** | ||
function calculateStatsPerFile(messages) { | ||
return messages.reduce((stat, message) => { | ||
const stat = { | ||
errorCount: 0, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
fixableErrorCount: 0, | ||
fixableWarningCount: 0 | ||
}; | ||
for (let i = 0; i < messages.length; i++) { | ||
const message = messages[i]; | ||
if (message.fatal || message.severity === 2) { | ||
@@ -177,10 +187,4 @@ stat.errorCount++; | ||
} | ||
return stat; | ||
}, { | ||
errorCount: 0, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
fixableErrorCount: 0, | ||
fixableWarningCount: 0 | ||
}); | ||
} | ||
return stat; | ||
} | ||
@@ -195,3 +199,13 @@ | ||
function calculateStatsPerRun(results) { | ||
return results.reduce((stat, result) => { | ||
const stat = { | ||
errorCount: 0, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
fixableErrorCount: 0, | ||
fixableWarningCount: 0 | ||
}; | ||
for (let i = 0; i < results.length; i++) { | ||
const result = results[i]; | ||
stat.errorCount += result.errorCount; | ||
@@ -202,10 +216,5 @@ stat.fatalErrorCount += result.fatalErrorCount; | ||
stat.fixableWarningCount += result.fixableWarningCount; | ||
return stat; | ||
}, { | ||
errorCount: 0, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
fixableErrorCount: 0, | ||
fixableWarningCount: 0 | ||
}); | ||
} | ||
return stat; | ||
} | ||
@@ -315,5 +324,7 @@ | ||
{ | ||
ruleId: null, | ||
fatal: false, | ||
severity: 1, | ||
message | ||
message, | ||
nodeType: null | ||
} | ||
@@ -623,4 +634,4 @@ ], | ||
loadRules, | ||
getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"), | ||
getEslintAllConfig: () => require("../../conf/eslint-all.js") | ||
getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, | ||
getEslintAllConfig: () => require("@eslint/js").configs.all | ||
}); | ||
@@ -627,0 +638,0 @@ const fileEnumerator = new FileEnumerator({ |
@@ -220,4 +220,4 @@ /** | ||
cwd, | ||
getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"), | ||
getEslintAllConfig: () => require("../../conf/eslint-all.js") | ||
getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, | ||
getEslintAllConfig: () => require("@eslint/js").configs.all | ||
}), | ||
@@ -353,3 +353,3 @@ extensions = null, | ||
if (globInputPaths && isGlobPattern(pattern)) { | ||
return this._iterateFilesWithGlob(absolutePath, isDot); | ||
return this._iterateFilesWithGlob(pattern, isDot); | ||
} | ||
@@ -403,4 +403,6 @@ | ||
const directoryPath = path.resolve(getGlobParent(pattern)); | ||
const globPart = pattern.slice(directoryPath.length + 1); | ||
const { cwd } = internalSlotsMap.get(this); | ||
const directoryPath = path.resolve(cwd, getGlobParent(pattern)); | ||
const absolutePath = path.resolve(cwd, pattern); | ||
const globPart = absolutePath.slice(directoryPath.length + 1); | ||
@@ -412,3 +414,3 @@ /* | ||
const recursive = /\*\*|\/|\\/u.test(globPart); | ||
const selector = new Minimatch(pattern, minimatchOpts); | ||
const selector = new Minimatch(absolutePath, minimatchOpts); | ||
@@ -415,0 +417,0 @@ debug(`recursive? ${recursive}`); |
@@ -22,3 +22,3 @@ /** | ||
{ ESLint } = require("./eslint"), | ||
{ FlatESLint } = require("./eslint/flat-eslint"), | ||
{ FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"), | ||
createCLIOptions = require("./options"), | ||
@@ -28,3 +28,2 @@ log = require("./shared/logging"), | ||
const { Legacy: { naming } } = require("@eslint/eslintrc"); | ||
const { findFlatConfigFile } = require("./eslint/flat-eslint"); | ||
const { ModuleImporter } = require("@humanwhocodes/module-importer"); | ||
@@ -97,3 +96,4 @@ | ||
rule, | ||
rulesdir | ||
rulesdir, | ||
warnIgnored | ||
}, configType) { | ||
@@ -189,2 +189,3 @@ | ||
options.ignorePatterns = ignorePattern; | ||
options.warnIgnored = warnIgnored; | ||
} else { | ||
@@ -282,27 +283,2 @@ options.resolvePluginsRelativeTo = resolvePluginsRelativeTo; | ||
/** | ||
* Returns whether flat config should be used. | ||
* @param {boolean} [allowFlatConfig] Whether or not to allow flat config. | ||
* @returns {Promise<boolean>} Where flat config should be used. | ||
*/ | ||
async function shouldUseFlatConfig(allowFlatConfig) { | ||
if (!allowFlatConfig) { | ||
return false; | ||
} | ||
switch (process.env.ESLINT_USE_FLAT_CONFIG) { | ||
case "true": | ||
return true; | ||
case "false": | ||
return false; | ||
default: | ||
/* | ||
* If neither explicitly enabled nor disabled, then use the presence | ||
* of a flat config file to determine enablement. | ||
*/ | ||
return !!(await findFlatConfigFile(process.cwd())); | ||
} | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -337,3 +313,3 @@ // Public Interface | ||
const usingFlatConfig = await shouldUseFlatConfig(allowFlatConfig); | ||
const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig(); | ||
@@ -420,3 +396,5 @@ debug("Using flat config?", usingFlatConfig); | ||
filePath: options.stdinFilename, | ||
warnIgnored: true | ||
// flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility | ||
warnIgnored: usingFlatConfig ? void 0 : true | ||
}); | ||
@@ -423,0 +401,0 @@ } else { |
@@ -22,5 +22,2 @@ /** | ||
"@": { | ||
parsers: { | ||
espree: require("espree") | ||
}, | ||
@@ -47,3 +44,3 @@ /* | ||
ecmaVersion: "latest", | ||
parser: "@/espree", | ||
parser: require("espree"), | ||
parserOptions: {} | ||
@@ -56,3 +53,3 @@ } | ||
ignores: [ | ||
"**/node_modules/**", | ||
"**/node_modules/", | ||
".git/" | ||
@@ -59,0 +56,0 @@ ] |
@@ -16,3 +16,3 @@ /** | ||
const { defaultConfig } = require("./default-config"); | ||
const recommendedConfig = require("../../conf/eslint-recommended"); | ||
const jsPlugin = require("@eslint/js"); | ||
@@ -40,2 +40,41 @@ //----------------------------------------------------------------------------- | ||
/** | ||
* Returns the name of an object in the config by reading its `meta` key. | ||
* @param {Object} object The object to check. | ||
* @returns {string?} The name of the object if found or `null` if there | ||
* is no name. | ||
*/ | ||
function getObjectId(object) { | ||
// first check old-style name | ||
let name = object.name; | ||
if (!name) { | ||
if (!object.meta) { | ||
return null; | ||
} | ||
name = object.meta.name; | ||
if (!name) { | ||
return null; | ||
} | ||
} | ||
// now check for old-style version | ||
let version = object.version; | ||
if (!version) { | ||
version = object.meta && object.meta.version; | ||
} | ||
// if there's a version then append that | ||
if (version) { | ||
return `${name}@${version}`; | ||
} | ||
return name; | ||
} | ||
const originalBaseConfig = Symbol("originalBaseConfig"); | ||
@@ -101,3 +140,9 @@ | ||
if (config === "eslint:recommended") { | ||
return recommendedConfig; | ||
// if we are in a Node.js environment warn the user | ||
if (typeof process !== "undefined" && process.emitWarning) { | ||
process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config."); | ||
} | ||
return jsPlugin.configs.recommended; | ||
} | ||
@@ -107,8 +152,8 @@ | ||
/* | ||
* Load `eslint-all.js` here instead of at the top level to avoid loading all rule modules | ||
* when it isn't necessary. `eslint-all.js` reads `meta` of rule objects to filter out deprecated ones, | ||
* so requiring `eslint-all.js` module loads all rule modules as a consequence. | ||
*/ | ||
return require("../../conf/eslint-all"); | ||
// if we are in a Node.js environment warn the user | ||
if (typeof process !== "undefined" && process.emitWarning) { | ||
process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config."); | ||
} | ||
return jsPlugin.configs.all; | ||
} | ||
@@ -152,12 +197,11 @@ | ||
if (languageOptions && languageOptions.parser) { | ||
if (typeof languageOptions.parser === "string") { | ||
const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser); | ||
const { parser } = languageOptions; | ||
parserName = languageOptions.parser; | ||
if (typeof parser === "object") { | ||
parserName = getObjectId(parser); | ||
if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) { | ||
throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`); | ||
if (!parserName) { | ||
invalidParser = true; | ||
} | ||
languageOptions.parser = plugins[pluginName].parsers[localParserName]; | ||
} else { | ||
@@ -180,2 +224,9 @@ invalidParser = true; | ||
config.processor = plugins[pluginName].processors[localProcessorName]; | ||
} else if (typeof processor === "object") { | ||
processorName = getObjectId(processor); | ||
if (!processorName) { | ||
invalidProcessor = true; | ||
} | ||
} else { | ||
@@ -194,7 +245,7 @@ invalidProcessor = true; | ||
if (invalidParser) { | ||
throw new Error("Caching is not supported when parser is an object."); | ||
throw new Error("Could not serialize parser object (missing 'meta' object)."); | ||
} | ||
if (invalidProcessor) { | ||
throw new Error("Caching is not supported when processor is an object."); | ||
throw new Error("Could not serialize processor object (missing 'meta' object)."); | ||
} | ||
@@ -204,3 +255,12 @@ | ||
...this, | ||
plugins: Object.keys(plugins), | ||
plugins: Object.entries(plugins).map(([namespace, plugin]) => { | ||
const pluginId = getObjectId(plugin); | ||
if (!pluginId) { | ||
return namespace; | ||
} | ||
return `${namespace}:${pluginId}`; | ||
}), | ||
languageOptions: { | ||
@@ -207,0 +267,0 @@ ...languageOptions, |
@@ -130,11 +130,27 @@ /** | ||
/** | ||
* The error type when a rule's options are configured with an invalid type. | ||
*/ | ||
class InvalidRuleOptionsError extends Error { | ||
/** | ||
* @param {string} ruleId Rule name being configured. | ||
* @param {any} value The invalid value. | ||
*/ | ||
constructor(ruleId, value) { | ||
super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`); | ||
this.messageTemplate = "invalid-rule-options"; | ||
this.messageData = { ruleId, value }; | ||
} | ||
} | ||
/** | ||
* Validates that a value is a valid rule options entry. | ||
* @param {string} ruleId Rule name being configured. | ||
* @param {any} value The value to check. | ||
* @returns {void} | ||
* @throws {TypeError} If the value isn't a valid rule options. | ||
* @throws {InvalidRuleOptionsError} If the value isn't a valid rule options. | ||
*/ | ||
function assertIsRuleOptions(value) { | ||
function assertIsRuleOptions(ruleId, value) { | ||
if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) { | ||
throw new TypeError("Expected a string, number, or array."); | ||
throw new InvalidRuleOptionsError(ruleId, value); | ||
} | ||
@@ -144,14 +160,29 @@ } | ||
/** | ||
* The error type when a rule's severity is invalid. | ||
*/ | ||
class InvalidRuleSeverityError extends Error { | ||
/** | ||
* @param {string} ruleId Rule name being configured. | ||
* @param {any} value The invalid value. | ||
*/ | ||
constructor(ruleId, value) { | ||
super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`); | ||
this.messageTemplate = "invalid-rule-severity"; | ||
this.messageData = { ruleId, value }; | ||
} | ||
} | ||
/** | ||
* Validates that a value is valid rule severity. | ||
* @param {string} ruleId Rule name being configured. | ||
* @param {any} value The value to check. | ||
* @returns {void} | ||
* @throws {TypeError} If the value isn't a valid rule severity. | ||
* @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity. | ||
*/ | ||
function assertIsRuleSeverity(value) { | ||
const severity = typeof value === "string" | ||
? ruleSeverities.get(value.toLowerCase()) | ||
: ruleSeverities.get(value); | ||
function assertIsRuleSeverity(ruleId, value) { | ||
const severity = ruleSeverities.get(value); | ||
if (typeof severity === "undefined") { | ||
throw new TypeError("Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); | ||
throw new InvalidRuleSeverityError(ruleId, value); | ||
} | ||
@@ -185,13 +216,33 @@ } | ||
/** | ||
* Validates that a value is an object or a string. | ||
* @param {any} value The value to check. | ||
* @returns {void} | ||
* @throws {TypeError} If the value isn't an object or a string. | ||
* The error type when there's an eslintrc-style options in a flat config. | ||
*/ | ||
function assertIsObjectOrString(value) { | ||
if ((!value || typeof value !== "object") && typeof value !== "string") { | ||
throw new TypeError("Expected an object or string."); | ||
class IncompatibleKeyError extends Error { | ||
/** | ||
* @param {string} key The invalid key. | ||
*/ | ||
constructor(key) { | ||
super("This appears to be in eslintrc format rather than flat config format."); | ||
this.messageTemplate = "eslintrc-incompat"; | ||
this.messageData = { key }; | ||
} | ||
} | ||
/** | ||
* The error type when there's an eslintrc-style plugins array found. | ||
*/ | ||
class IncompatiblePluginsError extends Error { | ||
/** | ||
* Creates a new instance. | ||
* @param {Array<string>} plugins The plugins array. | ||
*/ | ||
constructor(plugins) { | ||
super("This appears to be in eslintrc format (array of strings) rather than flat config format (object)."); | ||
this.messageTemplate = "eslintrc-plugins"; | ||
this.messageData = { plugins }; | ||
} | ||
} | ||
//----------------------------------------------------------------------------- | ||
@@ -248,11 +299,9 @@ // Low-Level Schemas | ||
validate(value) { | ||
assertIsObjectOrString(value); | ||
if (typeof value === "object" && typeof value.parse !== "function" && typeof value.parseForESLint !== "function") { | ||
throw new TypeError("Expected object to have a parse() or parseForESLint() method."); | ||
if (!value || typeof value !== "object" || | ||
(typeof value.parse !== "function" && typeof value.parseForESLint !== "function") | ||
) { | ||
throw new TypeError("Expected object with parse() or parseForESLint() method."); | ||
} | ||
if (typeof value === "string") { | ||
assertIsPluginMemberName(value); | ||
} | ||
} | ||
@@ -291,2 +340,7 @@ }; | ||
// make sure it's not an array, which would mean eslintrc-style is used | ||
if (Array.isArray(value)) { | ||
throw new IncompatiblePluginsError(value); | ||
} | ||
// second check the keys to make sure they are objects | ||
@@ -379,35 +433,24 @@ for (const key of Object.keys(value)) { | ||
let lastRuleId; | ||
/* | ||
* We are not checking the rule schema here because there is no | ||
* guarantee that the rule definition is present at this point. Instead | ||
* we wait and check the rule schema during the finalization step | ||
* of calculating a config. | ||
*/ | ||
for (const ruleId of Object.keys(value)) { | ||
// Performance: One try-catch has less overhead than one per loop iteration | ||
try { | ||
// avoid hairy edge case | ||
if (ruleId === "__proto__") { | ||
continue; | ||
} | ||
/* | ||
* We are not checking the rule schema here because there is no | ||
* guarantee that the rule definition is present at this point. Instead | ||
* we wait and check the rule schema during the finalization step | ||
* of calculating a config. | ||
*/ | ||
for (const ruleId of Object.keys(value)) { | ||
const ruleOptions = value[ruleId]; | ||
// avoid hairy edge case | ||
if (ruleId === "__proto__") { | ||
continue; | ||
} | ||
assertIsRuleOptions(ruleId, ruleOptions); | ||
lastRuleId = ruleId; | ||
const ruleOptions = value[ruleId]; | ||
assertIsRuleOptions(ruleOptions); | ||
if (Array.isArray(ruleOptions)) { | ||
assertIsRuleSeverity(ruleOptions[0]); | ||
} else { | ||
assertIsRuleSeverity(ruleOptions); | ||
} | ||
if (Array.isArray(ruleOptions)) { | ||
assertIsRuleSeverity(ruleId, ruleOptions[0]); | ||
} else { | ||
assertIsRuleSeverity(ruleId, ruleOptions); | ||
} | ||
} catch (error) { | ||
error.message = `Key "${lastRuleId}": ${error.message}`; | ||
throw error; | ||
} | ||
@@ -439,2 +482,30 @@ } | ||
/** | ||
* Creates a schema that always throws an error. Useful for warning | ||
* about eslintrc-style keys. | ||
* @param {string} key The eslintrc key to create a schema for. | ||
* @returns {ObjectPropertySchema} The schema. | ||
*/ | ||
function createEslintrcErrorSchema(key) { | ||
return { | ||
merge: "replace", | ||
validate() { | ||
throw new IncompatibleKeyError(key); | ||
} | ||
}; | ||
} | ||
const eslintrcKeys = [ | ||
"env", | ||
"extends", | ||
"globals", | ||
"ignorePatterns", | ||
"noInlineConfig", | ||
"overrides", | ||
"parser", | ||
"parserOptions", | ||
"reportUnusedDisableDirectives", | ||
"root" | ||
]; | ||
//----------------------------------------------------------------------------- | ||
@@ -444,3 +515,8 @@ // Full schema | ||
exports.flatConfigSchema = { | ||
const flatConfigSchema = { | ||
// eslintrc-style keys that should always error | ||
...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])), | ||
// flat config keys | ||
settings: deepObjectAssignSchema, | ||
@@ -466,1 +542,11 @@ linterOptions: { | ||
}; | ||
//----------------------------------------------------------------------------- | ||
// Exports | ||
//----------------------------------------------------------------------------- | ||
module.exports = { | ||
flatConfigSchema, | ||
assertIsRuleSeverity, | ||
assertIsRuleOptions | ||
}; |
@@ -12,3 +12,4 @@ /** | ||
const ajv = require("../shared/ajv")(); | ||
const ajvImport = require("../shared/ajv"); | ||
const ajv = ajvImport(); | ||
const { | ||
@@ -15,0 +16,0 @@ parseRuleId, |
@@ -226,3 +226,3 @@ /** | ||
* or an empty array if there are no matches. | ||
* @throws {UnmatchedSearchPatternsErrror} If there is a pattern that doesn't | ||
* @throws {UnmatchedSearchPatternsError} If there is a pattern that doesn't | ||
* match any files. | ||
@@ -530,5 +530,5 @@ */ | ||
// save patterns for later use based on whether globs are enabled | ||
if (globInputPaths && isGlobPattern(filePath)) { | ||
if (globInputPaths && isGlobPattern(pattern)) { | ||
const basePath = globParent(filePath); | ||
const basePath = path.resolve(cwd, globParent(pattern)); | ||
@@ -573,37 +573,2 @@ // group in cwd if possible and split out others | ||
/** | ||
* Checks whether a file exists at the given location | ||
* @param {string} resolvedPath A path from the CWD | ||
* @throws {Error} As thrown by `fs.statSync` or `fs.isFile`. | ||
* @returns {boolean} `true` if a file exists | ||
*/ | ||
function fileExists(resolvedPath) { | ||
try { | ||
return fs.statSync(resolvedPath).isFile(); | ||
} catch (error) { | ||
if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) { | ||
return false; | ||
} | ||
throw error; | ||
} | ||
} | ||
/** | ||
* Checks whether a directory exists at the given location | ||
* @param {string} resolvedPath A path from the CWD | ||
* @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`. | ||
* @returns {boolean} `true` if a directory exists | ||
*/ | ||
function directoryExists(resolvedPath) { | ||
try { | ||
return fs.statSync(resolvedPath).isDirectory(); | ||
} catch (error) { | ||
if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) { | ||
return false; | ||
} | ||
throw error; | ||
} | ||
} | ||
//----------------------------------------------------------------------------- | ||
@@ -632,12 +597,8 @@ // Results-related Helpers | ||
let message; | ||
const isHidden = filePath.split(path.sep) | ||
.find(segment => /^\./u.test(segment)); | ||
const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules"); | ||
const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules"); | ||
if (isHidden) { | ||
message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override."; | ||
} else if (isInNodeModules) { | ||
message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; | ||
if (isInNodeModules) { | ||
message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; | ||
} else { | ||
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."; | ||
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; | ||
} | ||
@@ -649,5 +610,7 @@ | ||
{ | ||
ruleId: null, | ||
fatal: false, | ||
severity: 1, | ||
message | ||
message, | ||
nodeType: null | ||
} | ||
@@ -721,2 +684,3 @@ ], | ||
reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. | ||
warnIgnored = true, | ||
...unknownOptions | ||
@@ -802,2 +766,5 @@ }) { | ||
} | ||
if (!isArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) { | ||
errors.push("'ignorePatterns' must be an array of non-empty strings or null."); | ||
} | ||
if (typeof overrideConfig !== "object") { | ||
@@ -825,2 +792,5 @@ errors.push("'overrideConfig' must be an object or null."); | ||
} | ||
if (typeof warnIgnored !== "boolean") { | ||
errors.push("'warnIgnored' must be a boolean."); | ||
} | ||
if (errors.length > 0) { | ||
@@ -840,3 +810,3 @@ throw new ESLintInvalidOptionsError(errors); | ||
overrideConfig, | ||
cwd, | ||
cwd: path.normalize(cwd), | ||
errorOnUnmatchedPattern, | ||
@@ -848,3 +818,4 @@ fix, | ||
ignorePatterns, | ||
reportUnusedDisableDirectives | ||
reportUnusedDisableDirectives, | ||
warnIgnored | ||
}; | ||
@@ -937,4 +908,2 @@ } | ||
isGlobPattern, | ||
directoryExists, | ||
fileExists, | ||
findFiles, | ||
@@ -941,0 +910,0 @@ |
@@ -292,3 +292,3 @@ /** | ||
configFile: overrideConfigFile, | ||
cwd, | ||
cwd: path.normalize(cwd), | ||
errorOnUnmatchedPattern, | ||
@@ -295,0 +295,0 @@ extensions, |
@@ -58,2 +58,3 @@ /** | ||
/** @typedef {import("../shared/types").LintMessage} LintMessage */ | ||
/** @typedef {import("../shared/types").LintResult} LintResult */ | ||
/** @typedef {import("../shared/types").ParserOptions} ParserOptions */ | ||
@@ -80,3 +81,3 @@ /** @typedef {import("../shared/types").Plugin} Plugin */ | ||
* @property {boolean} [ignore] False disables all ignore patterns except for the default ones. | ||
* @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. | ||
* @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`. | ||
* @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance | ||
@@ -88,2 +89,3 @@ * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy; | ||
* @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives. | ||
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files | ||
*/ | ||
@@ -99,2 +101,3 @@ | ||
const privateMembers = new WeakMap(); | ||
const importedConfigFileModificationTime = new Map(); | ||
@@ -108,3 +111,13 @@ /** | ||
function calculateStatsPerFile(messages) { | ||
return messages.reduce((stat, message) => { | ||
const stat = { | ||
errorCount: 0, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
fixableErrorCount: 0, | ||
fixableWarningCount: 0 | ||
}; | ||
for (let i = 0; i < messages.length; i++) { | ||
const message = messages[i]; | ||
if (message.fatal || message.severity === 2) { | ||
@@ -124,36 +137,7 @@ stat.errorCount++; | ||
} | ||
return stat; | ||
}, { | ||
errorCount: 0, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
fixableErrorCount: 0, | ||
fixableWarningCount: 0 | ||
}); | ||
} | ||
return stat; | ||
} | ||
/** | ||
* It will calculate the error and warning count for collection of results from all files | ||
* @param {LintResult[]} results Collection of messages from all the files | ||
* @returns {Object} Contains the stats | ||
* @private | ||
*/ | ||
function calculateStatsPerRun(results) { | ||
return results.reduce((stat, result) => { | ||
stat.errorCount += result.errorCount; | ||
stat.fatalErrorCount += result.fatalErrorCount; | ||
stat.warningCount += result.warningCount; | ||
stat.fixableErrorCount += result.fixableErrorCount; | ||
stat.fixableWarningCount += result.fixableWarningCount; | ||
return stat; | ||
}, { | ||
errorCount: 0, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
fixableErrorCount: 0, | ||
fixableWarningCount: 0 | ||
}); | ||
} | ||
/** | ||
* Create rulesMeta object. | ||
@@ -269,3 +253,3 @@ * @param {Map<string,Rule>} rules a map of rules from which to generate the object. | ||
* @param {string} cwd The current working directory to search from. | ||
* @returns {Promise<string|null>} The filename if found or `null` if not. | ||
* @returns {Promise<string|undefined>} The filename if found or `undefined` if not. | ||
*/ | ||
@@ -291,6 +275,80 @@ function findFlatConfigFile(cwd) { | ||
return (await import(fileURL)).default; | ||
const mtime = (await fs.stat(filePath)).mtime.getTime(); | ||
/* | ||
* Append a query with the config file's modification time (`mtime`) in order | ||
* to import the current version of the config file. Without the query, `import()` would | ||
* cache the config file module by the pathname only, and then always return | ||
* the same version (the one that was actual when the module was imported for the first time). | ||
* | ||
* This ensures that the config file module is loaded and executed again | ||
* if it has been changed since the last time it was imported. | ||
* If it hasn't been changed, `import()` will just return the cached version. | ||
* | ||
* Note that we should not overuse queries (e.g., by appending the current time | ||
* to always reload the config file module) as that could cause memory leaks | ||
* because entries are never removed from the import cache. | ||
*/ | ||
fileURL.searchParams.append("mtime", mtime); | ||
/* | ||
* With queries, we can bypass the import cache. However, when import-ing a CJS module, | ||
* Node.js uses the require infrastructure under the hood. That includes the require cache, | ||
* which caches the config file module by its file path (queries have no effect). | ||
* Therefore, we also need to clear the require cache before importing the config file module. | ||
* In order to get the same behavior with ESM and CJS config files, in particular - to reload | ||
* the config file only if it has been changed, we track file modification times and clear | ||
* the require cache only if the file has been changed. | ||
*/ | ||
if (importedConfigFileModificationTime.get(filePath) !== mtime) { | ||
delete require.cache[filePath]; | ||
} | ||
const config = (await import(fileURL)).default; | ||
importedConfigFileModificationTime.set(filePath, mtime); | ||
return config; | ||
} | ||
/** | ||
* Determines which config file to use. This is determined by seeing if an | ||
* override config file was passed, and if so, using it; otherwise, as long | ||
* as override config file is not explicitly set to `false`, it will search | ||
* upwards from the cwd for a file named `eslint.config.js`. | ||
* @param {import("./eslint").ESLintOptions} options The ESLint instance options. | ||
* @returns {{configFilePath:string|undefined,basePath:string,error:Error|null}} Location information for | ||
* the config file. | ||
*/ | ||
async function locateConfigFileToUse({ configFile, cwd }) { | ||
// determine where to load config file from | ||
let configFilePath; | ||
let basePath = cwd; | ||
let error = null; | ||
if (typeof configFile === "string") { | ||
debug(`Override config file path is ${configFile}`); | ||
configFilePath = path.resolve(cwd, configFile); | ||
} else if (configFile !== false) { | ||
debug("Searching for eslint.config.js"); | ||
configFilePath = await findFlatConfigFile(cwd); | ||
if (configFilePath) { | ||
basePath = path.resolve(path.dirname(configFilePath)); | ||
} else { | ||
error = new Error("Could not find config file."); | ||
} | ||
} | ||
return { | ||
configFilePath, | ||
basePath, | ||
error | ||
}; | ||
} | ||
/** | ||
* Calculates the config array for this run based on inputs. | ||
@@ -317,21 +375,9 @@ * @param {FlatESLint} eslint The instance to create the config array for. | ||
// determine where to load config file from | ||
let configFilePath; | ||
let basePath = cwd; | ||
const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd }); | ||
if (typeof configFile === "string") { | ||
debug(`Override config file path is ${configFile}`); | ||
configFilePath = path.resolve(cwd, configFile); | ||
} else if (configFile !== false) { | ||
debug("Searching for eslint.config.js"); | ||
configFilePath = await findFlatConfigFile(cwd); | ||
if (!configFilePath) { | ||
throw new Error("Could not find config file."); | ||
} | ||
basePath = path.resolve(path.dirname(configFilePath)); | ||
// config file is required to calculate config | ||
if (error) { | ||
throw error; | ||
} | ||
const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); | ||
@@ -353,35 +399,29 @@ | ||
let allIgnorePatterns = []; | ||
// append command line ignore patterns | ||
if (ignorePatterns && ignorePatterns.length > 0) { | ||
// append command line ignore patterns | ||
if (ignorePatterns) { | ||
if (typeof ignorePatterns === "string") { | ||
allIgnorePatterns.push(ignorePatterns); | ||
let relativeIgnorePatterns; | ||
/* | ||
* If the config file basePath is different than the cwd, then | ||
* the ignore patterns won't work correctly. Here, we adjust the | ||
* ignore pattern to include the correct relative path. Patterns | ||
* passed as `ignorePatterns` are relative to the cwd, whereas | ||
* the config file basePath can be an ancestor of the cwd. | ||
*/ | ||
if (basePath === cwd) { | ||
relativeIgnorePatterns = ignorePatterns; | ||
} else { | ||
allIgnorePatterns.push(...ignorePatterns); | ||
} | ||
} | ||
/* | ||
* If the config file basePath is different than the cwd, then | ||
* the ignore patterns won't work correctly. Here, we adjust the | ||
* ignore pattern to include the correct relative path. Patterns | ||
* loaded from ignore files are always relative to the cwd, whereas | ||
* the config file basePath can be an ancestor of the cwd. | ||
*/ | ||
if (basePath !== cwd && allIgnorePatterns.length) { | ||
const relativeIgnorePath = path.relative(basePath, cwd); | ||
const relativeIgnorePath = path.relative(basePath, cwd); | ||
relativeIgnorePatterns = ignorePatterns.map(pattern => { | ||
const negated = pattern.startsWith("!"); | ||
const basePattern = negated ? pattern.slice(1) : pattern; | ||
allIgnorePatterns = allIgnorePatterns.map(pattern => { | ||
const negated = pattern.startsWith("!"); | ||
const basePattern = negated ? pattern.slice(1) : pattern; | ||
return (negated ? "!" : "") + | ||
return (negated ? "!" : "") + | ||
path.posix.join(relativeIgnorePath, basePattern); | ||
}); | ||
} | ||
}); | ||
} | ||
if (allIgnorePatterns.length) { | ||
/* | ||
@@ -392,3 +432,3 @@ * Ignore patterns are added to the end of the config array | ||
configs.push({ | ||
ignores: allIgnorePatterns | ||
ignores: relativeIgnorePatterns | ||
}); | ||
@@ -507,39 +547,2 @@ } | ||
/** | ||
* Collect used deprecated rules. | ||
* @param {Array<FlatConfig>} configs The configs to evaluate. | ||
* @returns {IterableIterator<DeprecatedRuleInfo>} Used deprecated rules. | ||
*/ | ||
function *iterateRuleDeprecationWarnings(configs) { | ||
const processedRuleIds = new Set(); | ||
for (const config of configs) { | ||
for (const [ruleId, ruleConfig] of Object.entries(config.rules)) { | ||
// Skip if it was processed. | ||
if (processedRuleIds.has(ruleId)) { | ||
continue; | ||
} | ||
processedRuleIds.add(ruleId); | ||
// Skip if it's not used. | ||
if (!getRuleSeverity(ruleConfig)) { | ||
continue; | ||
} | ||
const rule = getRuleFromConfig(ruleId, config); | ||
// Skip if it's not deprecated. | ||
if (!(rule && rule.meta && rule.meta.deprecated)) { | ||
continue; | ||
} | ||
// This rule was used and deprecated. | ||
yield { | ||
ruleId, | ||
replacedBy: rule.meta.replacedBy || [] | ||
}; | ||
} | ||
} | ||
} | ||
/** | ||
* Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine. | ||
@@ -589,3 +592,2 @@ * @returns {TypeError} An error object. | ||
defaultConfigs, | ||
defaultIgnores: () => false, | ||
configs: null | ||
@@ -729,8 +731,6 @@ }); | ||
// ensure the rule exists | ||
if (!rule) { | ||
throw new TypeError(`Could not find the rule "${ruleId}".`); | ||
// ignore unknown rules | ||
if (rule) { | ||
resultRules.set(ruleId, rule); | ||
} | ||
resultRules.set(ruleId, rule); | ||
} | ||
@@ -767,6 +767,6 @@ } | ||
globInputPaths, | ||
errorOnUnmatchedPattern | ||
errorOnUnmatchedPattern, | ||
warnIgnored | ||
} = eslintOptions; | ||
const startTime = Date.now(); | ||
const usedConfigs = []; | ||
const fixTypesSet = fixTypes ? new Set(fixTypes) : null; | ||
@@ -815,3 +815,7 @@ | ||
if (ignored) { | ||
return createIgnoreResult(filePath, cwd); | ||
if (warnIgnored) { | ||
return createIgnoreResult(filePath, cwd); | ||
} | ||
return void 0; | ||
} | ||
@@ -830,11 +834,2 @@ | ||
/* | ||
* Store used configs for: | ||
* - this method uses to collect used deprecated rules. | ||
* - `--fix-type` option uses to get the loaded rule's meta data. | ||
*/ | ||
if (!usedConfigs.includes(config)) { | ||
usedConfigs.push(config); | ||
} | ||
// Skip if there is cached result. | ||
@@ -908,18 +903,6 @@ if (lintResultCache) { | ||
let usedDeprecatedRules; | ||
const finalResults = results.filter(result => !!result); | ||
return processLintReport(this, { | ||
results: finalResults, | ||
...calculateStatsPerRun(finalResults), | ||
// Initialize it lazily because CLI and `ESLint` API don't use it. | ||
get usedDeprecatedRules() { | ||
if (!usedDeprecatedRules) { | ||
usedDeprecatedRules = Array.from( | ||
iterateRuleDeprecationWarnings(usedConfigs) | ||
); | ||
} | ||
return usedDeprecatedRules; | ||
} | ||
results: finalResults | ||
}); | ||
@@ -952,3 +935,3 @@ } | ||
filePath, | ||
warnIgnored = false, | ||
warnIgnored, | ||
...unknownOptions | ||
@@ -967,3 +950,3 @@ } = options || {}; | ||
if (typeof warnIgnored !== "boolean") { | ||
if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") { | ||
throw new Error("'options.warnIgnored' must be a boolean or undefined"); | ||
@@ -983,3 +966,4 @@ } | ||
fix, | ||
reportUnusedDisableDirectives | ||
reportUnusedDisableDirectives, | ||
warnIgnored: constructorWarnIgnored | ||
} = eslintOptions; | ||
@@ -989,7 +973,8 @@ const results = []; | ||
const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js"); | ||
let config; | ||
// Clear the last used config arrays. | ||
if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) { | ||
if (warnIgnored) { | ||
const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored; | ||
if (shouldWarnIgnored) { | ||
results.push(createIgnoreResult(resolvedFilename, cwd)); | ||
@@ -999,5 +984,2 @@ } | ||
// TODO: Needed? | ||
config = configs.getConfig(resolvedFilename); | ||
// Do lint. | ||
@@ -1017,17 +999,5 @@ results.push(verifyText({ | ||
debug(`Linting complete in: ${Date.now() - startTime}ms`); | ||
let usedDeprecatedRules; | ||
return processLintReport(this, { | ||
results, | ||
...calculateStatsPerRun(results), | ||
// Initialize it lazily because CLI and `ESLint` API don't use it. | ||
get usedDeprecatedRules() { | ||
if (!usedDeprecatedRules) { | ||
usedDeprecatedRules = Array.from( | ||
iterateRuleDeprecationWarnings(config) | ||
); | ||
} | ||
return usedDeprecatedRules; | ||
} | ||
results | ||
}); | ||
@@ -1152,2 +1122,15 @@ | ||
/** | ||
* Finds the config file being used by this instance based on the options | ||
* passed to the constructor. | ||
* @returns {string|undefined} The path to the config file being used or | ||
* `undefined` if no config file is being used. | ||
*/ | ||
async findConfigFile() { | ||
const options = privateMembers.get(this).options; | ||
const { configFilePath } = await locateConfigFileToUse(options); | ||
return configFilePath; | ||
} | ||
/** | ||
* Checks if a given path is ignored by ESLint. | ||
@@ -1164,2 +1147,22 @@ * @param {string} filePath The path of the file to check. | ||
/** | ||
* Returns whether flat config should be used. | ||
* @returns {Promise<boolean>} Whether flat config should be used. | ||
*/ | ||
async function shouldUseFlatConfig() { | ||
switch (process.env.ESLINT_USE_FLAT_CONFIG) { | ||
case "true": | ||
return true; | ||
case "false": | ||
return false; | ||
default: | ||
/* | ||
* If neither explicitly enabled nor disabled, then use the presence | ||
* of a flat config file to determine enablement. | ||
*/ | ||
return !!(await findFlatConfigFile(process.cwd())); | ||
} | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -1171,3 +1174,3 @@ // Public Interface | ||
FlatESLint, | ||
findFlatConfigFile | ||
shouldUseFlatConfig | ||
}; |
@@ -8,2 +8,12 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Typedefs | ||
//------------------------------------------------------------------------------ | ||
/** @typedef {import("../shared/types").LintMessage} LintMessage */ | ||
//------------------------------------------------------------------------------ | ||
// Module Definition | ||
//------------------------------------------------------------------------------ | ||
const escapeRegExp = require("escape-string-regexp"); | ||
@@ -81,3 +91,3 @@ | ||
const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u"); | ||
const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u"); | ||
const match = regex.exec(listText); | ||
@@ -201,3 +211,3 @@ const matchedText = match[0]; | ||
* (this function always reports unused disable directives). | ||
* @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list | ||
* @returns {{problems: LintMessage[], unusedDisableDirectives: LintMessage[]}} An object with a list | ||
* of problems (including suppressed ones) and unused eslint-disable directives | ||
@@ -204,0 +214,0 @@ */ |
@@ -195,11 +195,14 @@ /** | ||
if (currentSegment !== headSegment && currentSegment) { | ||
debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`); | ||
if (currentSegment.reachable) { | ||
analyzer.emitter.emit( | ||
"onCodePathSegmentEnd", | ||
currentSegment, | ||
node | ||
); | ||
} | ||
const eventName = currentSegment.reachable | ||
? "onCodePathSegmentEnd" | ||
: "onUnreachableCodePathSegmentEnd"; | ||
debug.dump(`${eventName} ${currentSegment.id}`); | ||
analyzer.emitter.emit( | ||
eventName, | ||
currentSegment, | ||
node | ||
); | ||
} | ||
@@ -217,12 +220,15 @@ } | ||
if (currentSegment !== headSegment && headSegment) { | ||
debug.dump(`onCodePathSegmentStart ${headSegment.id}`); | ||
const eventName = headSegment.reachable | ||
? "onCodePathSegmentStart" | ||
: "onUnreachableCodePathSegmentStart"; | ||
debug.dump(`${eventName} ${headSegment.id}`); | ||
CodePathSegment.markUsed(headSegment); | ||
if (headSegment.reachable) { | ||
analyzer.emitter.emit( | ||
"onCodePathSegmentStart", | ||
headSegment, | ||
node | ||
); | ||
} | ||
analyzer.emitter.emit( | ||
eventName, | ||
headSegment, | ||
node | ||
); | ||
} | ||
@@ -246,11 +252,13 @@ } | ||
const currentSegment = currentSegments[i]; | ||
const eventName = currentSegment.reachable | ||
? "onCodePathSegmentEnd" | ||
: "onUnreachableCodePathSegmentEnd"; | ||
debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`); | ||
if (currentSegment.reachable) { | ||
analyzer.emitter.emit( | ||
"onCodePathSegmentEnd", | ||
currentSegment, | ||
node | ||
); | ||
} | ||
debug.dump(`${eventName} ${currentSegment.id}`); | ||
analyzer.emitter.emit( | ||
eventName, | ||
currentSegment, | ||
node | ||
); | ||
} | ||
@@ -257,0 +265,0 @@ |
/** | ||
* @fileoverview A class of the code path segment. | ||
* @fileoverview The CodePathSegment class. | ||
* @author Toru Nagashima | ||
@@ -33,2 +33,13 @@ */ | ||
* A code path segment. | ||
* | ||
* Each segment is arranged in a series of linked lists (implemented by arrays) | ||
* that keep track of the previous and next segments in a code path. In this way, | ||
* you can navigate between all segments in any code path so long as you have a | ||
* reference to any segment in that code path. | ||
* | ||
* When first created, the segment is in a detached state, meaning that it knows the | ||
* segments that came before it but those segments don't know that this new segment | ||
* follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it | ||
* officially become part of the code path by updating the previous segments to know | ||
* that this new segment follows. | ||
*/ | ||
@@ -38,2 +49,3 @@ class CodePathSegment { | ||
/** | ||
* Creates a new instance. | ||
* @param {string} id An identifier. | ||
@@ -54,3 +66,3 @@ * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. | ||
/** | ||
* An array of the next segments. | ||
* An array of the next reachable segments. | ||
* @type {CodePathSegment[]} | ||
@@ -61,3 +73,3 @@ */ | ||
/** | ||
* An array of the previous segments. | ||
* An array of the previous reachable segments. | ||
* @type {CodePathSegment[]} | ||
@@ -68,4 +80,3 @@ */ | ||
/** | ||
* An array of the next segments. | ||
* This array includes unreachable segments. | ||
* An array of all next segments including reachable and unreachable. | ||
* @type {CodePathSegment[]} | ||
@@ -76,4 +87,3 @@ */ | ||
/** | ||
* An array of the previous segments. | ||
* This array includes unreachable segments. | ||
* An array of all previous segments including reachable and unreachable. | ||
* @type {CodePathSegment[]} | ||
@@ -92,3 +102,7 @@ */ | ||
value: { | ||
// determines if the segment has been attached to the code path | ||
used: false, | ||
// array of previous segments coming from the end of a loop | ||
loopedPrevSegments: [] | ||
@@ -123,5 +137,6 @@ } | ||
/** | ||
* Creates a segment that follows given segments. | ||
* Creates a new segment and appends it after the given segments. | ||
* @param {string} id An identifier. | ||
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments. | ||
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments | ||
* to append to. | ||
* @returns {CodePathSegment} The created segment. | ||
@@ -138,3 +153,3 @@ */ | ||
/** | ||
* Creates an unreachable segment that follows given segments. | ||
* Creates an unreachable segment and appends it after the given segments. | ||
* @param {string} id An identifier. | ||
@@ -149,3 +164,3 @@ * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. | ||
* In `if (a) return a; foo();` case, the unreachable segment preceded by | ||
* the return statement is not used but must not be remove. | ||
* the return statement is not used but must not be removed. | ||
*/ | ||
@@ -170,3 +185,3 @@ CodePathSegment.markUsed(segment); | ||
/** | ||
* Makes a given segment being used. | ||
* Marks a given segment as used. | ||
* | ||
@@ -186,2 +201,9 @@ * And this function registers the segment into the previous segments as a next. | ||
if (segment.reachable) { | ||
/* | ||
* If the segment is reachable, then it's officially part of the | ||
* code path. This loops through all previous segments to update | ||
* their list of next segments. Because the segment is reachable, | ||
* it's added to both `nextSegments` and `allNextSegments`. | ||
*/ | ||
for (i = 0; i < segment.allPrevSegments.length; ++i) { | ||
@@ -194,2 +216,9 @@ const prevSegment = segment.allPrevSegments[i]; | ||
} else { | ||
/* | ||
* If the segment is not reachable, then it's not officially part of the | ||
* code path. This loops through all previous segments to update | ||
* their list of next segments. Because the segment is not reachable, | ||
* it's added only to `allNextSegments`. | ||
*/ | ||
for (i = 0; i < segment.allPrevSegments.length; ++i) { | ||
@@ -212,9 +241,10 @@ segment.allPrevSegments[i].allNextSegments.push(segment); | ||
/** | ||
* Replaces unused segments with the previous segments of each unused segment. | ||
* @param {CodePathSegment[]} segments An array of segments to replace. | ||
* @returns {CodePathSegment[]} The replaced array. | ||
* Creates a new array based on an array of segments. If any segment in the | ||
* array is unused, then it is replaced by all of its previous segments. | ||
* All used segments are returned as-is without replacement. | ||
* @param {CodePathSegment[]} segments The array of segments to flatten. | ||
* @returns {CodePathSegment[]} The flattened array. | ||
*/ | ||
static flattenUnusedSegments(segments) { | ||
const done = Object.create(null); | ||
const retv = []; | ||
const done = new Set(); | ||
@@ -225,3 +255,3 @@ for (let i = 0; i < segments.length; ++i) { | ||
// Ignores duplicated. | ||
if (done[segment.id]) { | ||
if (done.has(segment)) { | ||
continue; | ||
@@ -235,14 +265,12 @@ } | ||
if (!done[prevSegment.id]) { | ||
done[prevSegment.id] = true; | ||
retv.push(prevSegment); | ||
if (!done.has(prevSegment)) { | ||
done.add(prevSegment); | ||
} | ||
} | ||
} else { | ||
done[segment.id] = true; | ||
retv.push(segment); | ||
done.add(segment); | ||
} | ||
} | ||
return retv; | ||
return [...done]; | ||
} | ||
@@ -249,0 +277,0 @@ } |
@@ -83,3 +83,5 @@ /** | ||
/** | ||
* The initial code path segment. | ||
* The initial code path segment. This is the segment that is at the head | ||
* of the code path. | ||
* This is a passthrough to the underlying `CodePathState`. | ||
* @type {CodePathSegment} | ||
@@ -92,4 +94,6 @@ */ | ||
/** | ||
* Final code path segments. | ||
* This array is a mix of `returnedSegments` and `thrownSegments`. | ||
* Final code path segments. These are the terminal (tail) segments in the | ||
* code path, which is the combination of `returnedSegments` and `thrownSegments`. | ||
* All segments in this array are reachable. | ||
* This is a passthrough to the underlying `CodePathState`. | ||
* @type {CodePathSegment[]} | ||
@@ -102,5 +106,10 @@ */ | ||
/** | ||
* Final code path segments which is with `return` statements. | ||
* This array contains the last path segment if it's reachable. | ||
* Since the reachable last path returns `undefined`. | ||
* Final code path segments that represent normal completion of the code path. | ||
* For functions, this means both explicit `return` statements and implicit returns, | ||
* such as the last reachable segment in a function that does not have an | ||
* explicit `return` as this implicitly returns `undefined`. For scripts, | ||
* modules, class field initializers, and class static blocks, this means | ||
* all lines of code have been executed. | ||
* These segments are also present in `finalSegments`. | ||
* This is a passthrough to the underlying `CodePathState`. | ||
* @type {CodePathSegment[]} | ||
@@ -113,3 +122,5 @@ */ | ||
/** | ||
* Final code path segments which is with `throw` statements. | ||
* Final code path segments that represent `throw` statements. | ||
* This is a passthrough to the underlying `CodePathState`. | ||
* These segments are also present in `finalSegments`. | ||
* @type {CodePathSegment[]} | ||
@@ -122,4 +133,10 @@ */ | ||
/** | ||
* Current code path segments. | ||
* Tracks the traversal of the code path through each segment. This array | ||
* starts empty and segments are added or removed as the code path is | ||
* traversed. This array always ends up empty at the end of a code path | ||
* traversal. The `CodePathState` uses this to track its progress through | ||
* the code path. | ||
* This is a passthrough to the underlying `CodePathState`. | ||
* @type {CodePathSegment[]} | ||
* @deprecated | ||
*/ | ||
@@ -133,3 +150,3 @@ get currentSegments() { | ||
* | ||
* codePath.traverseSegments(function(segment, controller) { | ||
* codePath.traverseSegments((segment, controller) => { | ||
* // do something. | ||
@@ -140,36 +157,60 @@ * }); | ||
* | ||
* The `controller` object has two methods. | ||
* The `controller` argument has two methods: | ||
* | ||
* - `controller.skip()` - Skip the following segments in this branch. | ||
* - `controller.break()` - Skip all following segments. | ||
* @param {Object} [options] Omittable. | ||
* @param {CodePathSegment} [options.first] The first segment to traverse. | ||
* @param {CodePathSegment} [options.last] The last segment to traverse. | ||
* - `skip()` - skips the following segments in this branch | ||
* - `break()` - skips all following segments in the traversal | ||
* | ||
* A note on the parameters: the `options` argument is optional. This means | ||
* the first argument might be an options object or the callback function. | ||
* @param {Object} [optionsOrCallback] Optional first and last segments to traverse. | ||
* @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse. | ||
* @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse. | ||
* @param {Function} callback A callback function. | ||
* @returns {void} | ||
*/ | ||
traverseSegments(options, callback) { | ||
traverseSegments(optionsOrCallback, callback) { | ||
// normalize the arguments into a callback and options | ||
let resolvedOptions; | ||
let resolvedCallback; | ||
if (typeof options === "function") { | ||
resolvedCallback = options; | ||
if (typeof optionsOrCallback === "function") { | ||
resolvedCallback = optionsOrCallback; | ||
resolvedOptions = {}; | ||
} else { | ||
resolvedOptions = options || {}; | ||
resolvedOptions = optionsOrCallback || {}; | ||
resolvedCallback = callback; | ||
} | ||
// determine where to start traversing from based on the options | ||
const startSegment = resolvedOptions.first || this.internal.initialSegment; | ||
const lastSegment = resolvedOptions.last; | ||
let item = null; | ||
// set up initial location information | ||
let record = null; | ||
let index = 0; | ||
let end = 0; | ||
let segment = null; | ||
const visited = Object.create(null); | ||
// segments that have already been visited during traversal | ||
const visited = new Set(); | ||
// tracks the traversal steps | ||
const stack = [[startSegment, 0]]; | ||
// tracks the last skipped segment during traversal | ||
let skippedSegment = null; | ||
// indicates if we exited early from the traversal | ||
let broken = false; | ||
/** | ||
* Maintains traversal state. | ||
*/ | ||
const controller = { | ||
/** | ||
* Skip the following segments in this branch. | ||
* @returns {void} | ||
*/ | ||
skip() { | ||
@@ -182,2 +223,8 @@ if (stack.length <= 1) { | ||
}, | ||
/** | ||
* Stop traversal completely - do not traverse to any | ||
* other segments. | ||
* @returns {void} | ||
*/ | ||
break() { | ||
@@ -189,3 +236,3 @@ broken = true; | ||
/** | ||
* Checks a given previous segment has been visited. | ||
* Checks if a given previous segment has been visited. | ||
* @param {CodePathSegment} prevSegment A previous segment to check. | ||
@@ -196,3 +243,3 @@ * @returns {boolean} `true` if the segment has been visited. | ||
return ( | ||
visited[prevSegment.id] || | ||
visited.has(prevSegment) || | ||
segment.isLoopedPrevSegment(prevSegment) | ||
@@ -202,11 +249,25 @@ ); | ||
// the traversal | ||
while (stack.length > 0) { | ||
item = stack[stack.length - 1]; | ||
segment = item[0]; | ||
index = item[1]; | ||
/* | ||
* This isn't a pure stack. We use the top record all the time | ||
* but don't always pop it off. The record is popped only if | ||
* one of the following is true: | ||
* | ||
* 1) We have already visited the segment. | ||
* 2) We have not visited *all* of the previous segments. | ||
* 3) We have traversed past the available next segments. | ||
* | ||
* Otherwise, we just read the value and sometimes modify the | ||
* record as we traverse. | ||
*/ | ||
record = stack[stack.length - 1]; | ||
segment = record[0]; | ||
index = record[1]; | ||
if (index === 0) { | ||
// Skip if this segment has been visited already. | ||
if (visited[segment.id]) { | ||
if (visited.has(segment)) { | ||
stack.pop(); | ||
@@ -225,14 +286,25 @@ continue; | ||
// Reset the flag of skipping if all branches have been skipped. | ||
// Reset the skipping flag if all branches have been skipped. | ||
if (skippedSegment && segment.prevSegments.includes(skippedSegment)) { | ||
skippedSegment = null; | ||
} | ||
visited[segment.id] = true; | ||
visited.add(segment); | ||
// Call the callback when the first time. | ||
/* | ||
* If the most recent segment hasn't been skipped, then we call | ||
* the callback, passing in the segment and the controller. | ||
*/ | ||
if (!skippedSegment) { | ||
resolvedCallback.call(this, segment, controller); | ||
// exit if we're at the last segment | ||
if (segment === lastSegment) { | ||
controller.skip(); | ||
} | ||
/* | ||
* If the previous statement was executed, or if the callback | ||
* called a method on the controller, we might need to exit the | ||
* loop, so check for that and break accordingly. | ||
*/ | ||
if (broken) { | ||
@@ -247,8 +319,31 @@ break; | ||
if (index < end) { | ||
item[1] += 1; | ||
/* | ||
* If we haven't yet visited all of the next segments, update | ||
* the current top record on the stack to the next index to visit | ||
* and then push a record for the current segment on top. | ||
* | ||
* Setting the current top record's index lets us know how many | ||
* times we've been here and ensures that the segment won't be | ||
* reprocessed (because we only process segments with an index | ||
* of 0). | ||
*/ | ||
record[1] += 1; | ||
stack.push([segment.nextSegments[index], 0]); | ||
} else if (index === end) { | ||
item[0] = segment.nextSegments[index]; | ||
item[1] = 0; | ||
/* | ||
* If we are at the last next segment, then reset the top record | ||
* in the stack to next segment and set its index to 0 so it will | ||
* be processed next. | ||
*/ | ||
record[0] = segment.nextSegments[index]; | ||
record[1] = 0; | ||
} else { | ||
/* | ||
* If index > end, that means we have no more segments that need | ||
* processing. So, we pop that record off of the stack in order to | ||
* continue traversing at the next level up. | ||
*/ | ||
stack.pop(); | ||
@@ -255,0 +350,0 @@ } |
@@ -112,3 +112,3 @@ /** | ||
if (codePath.thrownSegments.length > 0) { | ||
text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize];\n"; | ||
text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n"; | ||
} | ||
@@ -115,0 +115,0 @@ |
@@ -24,4 +24,4 @@ /** | ||
/** | ||
* Gets whether or not a given segment is reachable. | ||
* @param {CodePathSegment} segment A segment to get. | ||
* Determines whether or not a given segment is reachable. | ||
* @param {CodePathSegment} segment The segment to check. | ||
* @returns {boolean} `true` if the segment is reachable. | ||
@@ -34,22 +34,53 @@ */ | ||
/** | ||
* Creates new segments from the specific range of `context.segmentsList`. | ||
* Creates a new segment for each fork in the given context and appends it | ||
* to the end of the specified range of segments. Ultimately, this ends up calling | ||
* `new CodePathSegment()` for each of the forks using the `create` argument | ||
* as a wrapper around special behavior. | ||
* | ||
* The `startIndex` and `endIndex` arguments specify a range of segments in | ||
* `context` that should become `allPrevSegments` for the newly created | ||
* `CodePathSegment` objects. | ||
* | ||
* When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and | ||
* `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`. | ||
* This `h` is from `b`, `d`, and `f`. | ||
* @param {ForkContext} context An instance. | ||
* @param {number} begin The first index of the previous segments. | ||
* @param {number} end The last index of the previous segments. | ||
* @param {Function} create A factory function of new segments. | ||
* @returns {CodePathSegment[]} New segments. | ||
* `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to | ||
* the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of | ||
* `b`, `d`, and `f`. | ||
* @param {ForkContext} context An instance from which the previous segments | ||
* will be obtained. | ||
* @param {number} startIndex The index of the first segment in the context | ||
* that should be specified as previous segments for the newly created segments. | ||
* @param {number} endIndex The index of the last segment in the context | ||
* that should be specified as previous segments for the newly created segments. | ||
* @param {Function} create A function that creates new `CodePathSegment` | ||
* instances in a particular way. See the `CodePathSegment.new*` methods. | ||
* @returns {Array<CodePathSegment>} An array of the newly-created segments. | ||
*/ | ||
function makeSegments(context, begin, end, create) { | ||
function createSegments(context, startIndex, endIndex, create) { | ||
/** @type {Array<Array<CodePathSegment>>} */ | ||
const list = context.segmentsList; | ||
const normalizedBegin = begin >= 0 ? begin : list.length + begin; | ||
const normalizedEnd = end >= 0 ? end : list.length + end; | ||
/* | ||
* Both `startIndex` and `endIndex` work the same way: if the number is zero | ||
* or more, then the number is used as-is. If the number is negative, | ||
* then that number is added to the length of the segments list to | ||
* determine the index to use. That means -1 for either argument | ||
* is the last element, -2 is the second to last, and so on. | ||
* | ||
* So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the | ||
* effective `startIndex` is 0 and the effective `endIndex` is 2, so this function | ||
* will include items at indices 0, 1, and 2. | ||
* | ||
* Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only | ||
* be using the last segment in `list`. | ||
*/ | ||
const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex; | ||
const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex; | ||
/** @type {Array<CodePathSegment>} */ | ||
const segments = []; | ||
for (let i = 0; i < context.count; ++i) { | ||
// this is passed into `new CodePathSegment` to add to code path. | ||
const allPrevSegments = []; | ||
@@ -61,2 +92,3 @@ | ||
// note: `create` is just a wrapper that augments `new CodePathSegment`. | ||
segments.push(create(context.idGenerator.next(), allPrevSegments)); | ||
@@ -69,9 +101,8 @@ } | ||
/** | ||
* `segments` becomes doubly in a `finally` block. Then if a code path exits by a | ||
* control statement (such as `break`, `continue`) from the `finally` block, the | ||
* destination's segments may be half of the source segments. In that case, this | ||
* merges segments. | ||
* @param {ForkContext} context An instance. | ||
* @param {CodePathSegment[]} segments Segments to merge. | ||
* @returns {CodePathSegment[]} The merged segments. | ||
* Inside of a `finally` block we end up with two parallel paths. If the code path | ||
* exits by a control statement (such as `break` or `continue`) from the `finally` | ||
* block, then we need to merge the remaining parallel paths back into one. | ||
* @param {ForkContext} context The fork context to work on. | ||
* @param {Array<CodePathSegment>} segments Segments to merge. | ||
* @returns {Array<CodePathSegment>} The merged segments. | ||
*/ | ||
@@ -81,6 +112,29 @@ function mergeExtraSegments(context, segments) { | ||
/* | ||
* We need to ensure that the array returned from this function contains no more | ||
* than the number of segments that the context allows. `context.count` indicates | ||
* how many items should be in the returned array to ensure that the new segment | ||
* entries will line up with the already existing segment entries. | ||
*/ | ||
while (currentSegments.length > context.count) { | ||
const merged = []; | ||
for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) { | ||
/* | ||
* Because `context.count` is a factor of 2 inside of a `finally` block, | ||
* we can divide the segment count by 2 to merge the paths together. | ||
* This loops through each segment in the list and creates a new `CodePathSegment` | ||
* that has the segment and the segment two slots away as previous segments. | ||
* | ||
* If `currentSegments` is [a,b,c,d], this will create new segments e and f, such | ||
* that: | ||
* | ||
* When `i` is 0: | ||
* a->e | ||
* c->e | ||
* | ||
* When `i` is 1: | ||
* b->f | ||
* d->f | ||
*/ | ||
for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) { | ||
merged.push(CodePathSegment.newNext( | ||
@@ -91,4 +145,11 @@ context.idGenerator.next(), | ||
} | ||
/* | ||
* Go through the loop condition one more time to see if we have the | ||
* number of segments for the context. If not, we'll keep merging paths | ||
* of the merged segments until we get there. | ||
*/ | ||
currentSegments = merged; | ||
} | ||
return currentSegments; | ||
@@ -102,3 +163,3 @@ } | ||
/** | ||
* A class to manage forking. | ||
* Manages the forking of code paths. | ||
*/ | ||
@@ -108,10 +169,40 @@ class ForkContext { | ||
/** | ||
* Creates a new instance. | ||
* @param {IdGenerator} idGenerator An identifier generator for segments. | ||
* @param {ForkContext|null} upper An upper fork context. | ||
* @param {number} count A number of parallel segments. | ||
* @param {ForkContext|null} upper The preceding fork context. | ||
* @param {number} count The number of parallel segments in each element | ||
* of `segmentsList`. | ||
*/ | ||
constructor(idGenerator, upper, count) { | ||
/** | ||
* The ID generator that will generate segment IDs for any new | ||
* segments that are created. | ||
* @type {IdGenerator} | ||
*/ | ||
this.idGenerator = idGenerator; | ||
/** | ||
* The preceding fork context. | ||
* @type {ForkContext|null} | ||
*/ | ||
this.upper = upper; | ||
/** | ||
* The number of elements in each element of `segmentsList`. In most | ||
* cases, this is 1 but can be 2 when there is a `finally` present, | ||
* which forks the code path outside of normal flow. In the case of nested | ||
* `finally` blocks, this can be a multiple of 2. | ||
* @type {number} | ||
*/ | ||
this.count = count; | ||
/** | ||
* The segments within this context. Each element in this array has | ||
* `count` elements that represent one step in each fork. For example, | ||
* when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path | ||
* a->c->e and one path b->d->f, and `count` is 2 because each element | ||
* is an array with two elements. | ||
* @type {Array<Array<CodePathSegment>>} | ||
*/ | ||
this.segmentsList = []; | ||
@@ -121,4 +212,4 @@ } | ||
/** | ||
* The head segments. | ||
* @type {CodePathSegment[]} | ||
* The segments that begin this fork context. | ||
* @type {Array<CodePathSegment>} | ||
*/ | ||
@@ -132,3 +223,3 @@ get head() { | ||
/** | ||
* A flag which shows empty. | ||
* Indicates if the context contains no segments. | ||
* @type {boolean} | ||
@@ -141,3 +232,3 @@ */ | ||
/** | ||
* A flag which shows reachable. | ||
* Indicates if there are any segments that are reachable. | ||
* @type {boolean} | ||
@@ -152,38 +243,49 @@ */ | ||
/** | ||
* Creates new segments from this context. | ||
* @param {number} begin The first index of previous segments. | ||
* @param {number} end The last index of previous segments. | ||
* @returns {CodePathSegment[]} New segments. | ||
* Creates new segments in this context and appends them to the end of the | ||
* already existing `CodePathSegment`s specified by `startIndex` and | ||
* `endIndex`. | ||
* @param {number} startIndex The index of the first segment in the context | ||
* that should be specified as previous segments for the newly created segments. | ||
* @param {number} endIndex The index of the last segment in the context | ||
* that should be specified as previous segments for the newly created segments. | ||
* @returns {Array<CodePathSegment>} An array of the newly created segments. | ||
*/ | ||
makeNext(begin, end) { | ||
return makeSegments(this, begin, end, CodePathSegment.newNext); | ||
makeNext(startIndex, endIndex) { | ||
return createSegments(this, startIndex, endIndex, CodePathSegment.newNext); | ||
} | ||
/** | ||
* Creates new segments from this context. | ||
* The new segments is always unreachable. | ||
* @param {number} begin The first index of previous segments. | ||
* @param {number} end The last index of previous segments. | ||
* @returns {CodePathSegment[]} New segments. | ||
* Creates new unreachable segments in this context and appends them to the end of the | ||
* already existing `CodePathSegment`s specified by `startIndex` and | ||
* `endIndex`. | ||
* @param {number} startIndex The index of the first segment in the context | ||
* that should be specified as previous segments for the newly created segments. | ||
* @param {number} endIndex The index of the last segment in the context | ||
* that should be specified as previous segments for the newly created segments. | ||
* @returns {Array<CodePathSegment>} An array of the newly created segments. | ||
*/ | ||
makeUnreachable(begin, end) { | ||
return makeSegments(this, begin, end, CodePathSegment.newUnreachable); | ||
makeUnreachable(startIndex, endIndex) { | ||
return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable); | ||
} | ||
/** | ||
* Creates new segments from this context. | ||
* The new segments don't have connections for previous segments. | ||
* But these inherit the reachable flag from this context. | ||
* @param {number} begin The first index of previous segments. | ||
* @param {number} end The last index of previous segments. | ||
* @returns {CodePathSegment[]} New segments. | ||
* Creates new segments in this context and does not append them to the end | ||
* of the already existing `CodePathSegment`s specified by `startIndex` and | ||
* `endIndex`. The `startIndex` and `endIndex` are only used to determine if | ||
* the new segments should be reachable. If any of the segments in this range | ||
* are reachable then the new segments are also reachable; otherwise, the new | ||
* segments are unreachable. | ||
* @param {number} startIndex The index of the first segment in the context | ||
* that should be considered for reachability. | ||
* @param {number} endIndex The index of the last segment in the context | ||
* that should be considered for reachability. | ||
* @returns {Array<CodePathSegment>} An array of the newly created segments. | ||
*/ | ||
makeDisconnected(begin, end) { | ||
return makeSegments(this, begin, end, CodePathSegment.newDisconnected); | ||
makeDisconnected(startIndex, endIndex) { | ||
return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected); | ||
} | ||
/** | ||
* Adds segments into this context. | ||
* The added segments become the head. | ||
* @param {CodePathSegment[]} segments Segments to add. | ||
* Adds segments to the head of this context. | ||
* @param {Array<CodePathSegment>} segments The segments to add. | ||
* @returns {void} | ||
@@ -193,3 +295,2 @@ */ | ||
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); | ||
this.segmentsList.push(mergeExtraSegments(this, segments)); | ||
@@ -199,11 +300,13 @@ } | ||
/** | ||
* Replaces the head segments with given segments. | ||
* Replaces the head segments with the given segments. | ||
* The current head segments are removed. | ||
* @param {CodePathSegment[]} segments Segments to add. | ||
* @param {Array<CodePathSegment>} replacementHeadSegments The new head segments. | ||
* @returns {void} | ||
*/ | ||
replaceHead(segments) { | ||
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); | ||
this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); | ||
replaceHead(replacementHeadSegments) { | ||
assert( | ||
replacementHeadSegments.length >= this.count, | ||
`${replacementHeadSegments.length} >= ${this.count}` | ||
); | ||
this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments)); | ||
} | ||
@@ -213,13 +316,8 @@ | ||
* Adds all segments of a given fork context into this context. | ||
* @param {ForkContext} context A fork context to add. | ||
* @param {ForkContext} otherForkContext The fork context to add from. | ||
* @returns {void} | ||
*/ | ||
addAll(context) { | ||
assert(context.count === this.count); | ||
const source = context.segmentsList; | ||
for (let i = 0; i < source.length; ++i) { | ||
this.segmentsList.push(source[i]); | ||
} | ||
addAll(otherForkContext) { | ||
assert(otherForkContext.count === this.count); | ||
this.segmentsList.push(...otherForkContext.segmentsList); | ||
} | ||
@@ -236,3 +334,4 @@ | ||
/** | ||
* Creates the root fork context. | ||
* Creates a new root context, meaning that there are no parent | ||
* fork contexts. | ||
* @param {IdGenerator} idGenerator An identifier generator for segments. | ||
@@ -252,10 +351,12 @@ * @returns {ForkContext} New fork context. | ||
* @param {ForkContext} parentContext The parent fork context. | ||
* @param {boolean} forkLeavingPath A flag which shows inside of `finally` block. | ||
* @param {boolean} shouldForkLeavingPath Indicates that we are inside of | ||
* a `finally` block and should therefore fork the path that leaves | ||
* `finally`. | ||
* @returns {ForkContext} New fork context. | ||
*/ | ||
static newEmpty(parentContext, forkLeavingPath) { | ||
static newEmpty(parentContext, shouldForkLeavingPath) { | ||
return new ForkContext( | ||
parentContext.idGenerator, | ||
parentContext, | ||
(forkLeavingPath ? 2 : 1) * parentContext.count | ||
(shouldForkLeavingPath ? 2 : 1) * parentContext.count | ||
); | ||
@@ -262,0 +363,0 @@ } |
@@ -23,2 +23,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Typedefs | ||
//------------------------------------------------------------------------------ | ||
/** @typedef {import("../shared/types").LintMessage} LintMessage */ | ||
//------------------------------------------------------------------------------ | ||
// Public Interface | ||
@@ -65,3 +71,3 @@ //------------------------------------------------------------------------------ | ||
* @param {Object} location Start line and column of comments for potential error message. | ||
* @returns {({success: true, config: Object}|{success: false, error: Problem})} Result map object | ||
* @returns {({success: true, config: Object}|{success: false, error: LintMessage})} Result map object | ||
*/ | ||
@@ -114,3 +120,4 @@ parseJsonConfig(string, location) { | ||
line: location.start.line, | ||
column: location.start.column + 1 | ||
column: location.start.column + 1, | ||
nodeType: null | ||
} | ||
@@ -138,3 +145,3 @@ }; | ||
string.split(",").forEach(name => { | ||
const trimmedName = name.trim(); | ||
const trimmedName = name.trim().replace(/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/us, "$<ruleId>"); | ||
@@ -141,0 +148,0 @@ if (trimmedName) { |
@@ -20,2 +20,4 @@ /** | ||
/** @typedef {import("../shared/types").LintMessage} LintMessage */ | ||
/** | ||
@@ -33,19 +35,2 @@ * An error message description | ||
/** | ||
* Information about the report | ||
* @typedef {Object} ReportInfo | ||
* @property {string} ruleId The rule ID | ||
* @property {(0|1|2)} severity Severity of the error | ||
* @property {(string|undefined)} message The message | ||
* @property {(string|undefined)} [messageId] The message ID | ||
* @property {number} line The line number | ||
* @property {number} column The column number | ||
* @property {(number|undefined)} [endLine] The ending line number | ||
* @property {(number|undefined)} [endColumn] The ending column number | ||
* @property {(string|null)} nodeType Type of node | ||
* @property {string} source Source text | ||
* @property {({text: string, range: (number[]|null)}|null)} [fix] The fix object | ||
* @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions] Suggestion info | ||
*/ | ||
//------------------------------------------------------------------------------ | ||
@@ -121,2 +106,18 @@ // Module Definition | ||
/** | ||
* Clones the given fix object. | ||
* @param {Fix|null} fix The fix to clone. | ||
* @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in. | ||
*/ | ||
function cloneFix(fix) { | ||
if (!fix) { | ||
return null; | ||
} | ||
return { | ||
range: [fix.range[0], fix.range[1]], | ||
text: fix.text | ||
}; | ||
} | ||
/** | ||
* Check that a fix has a valid range. | ||
@@ -158,3 +159,3 @@ * @param {Fix|null} fix The fix to validate. | ||
if (fixes.length === 1) { | ||
return fixes[0]; | ||
return cloneFix(fixes[0]); | ||
} | ||
@@ -205,3 +206,3 @@ | ||
assertValidFix(fix); | ||
return fix; | ||
return cloneFix(fix); | ||
} | ||
@@ -247,3 +248,3 @@ | ||
* @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects | ||
* @returns {function(...args): ReportInfo} Function that returns information about the report | ||
* @returns {LintMessage} Information about the report | ||
*/ | ||
@@ -323,3 +324,3 @@ function createProblem(options) { | ||
* @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted | ||
* @returns {function(...args): ReportInfo} Function that returns information about the report | ||
* @returns {function(...args): LintMessage} Function that returns information about the report | ||
*/ | ||
@@ -326,0 +327,0 @@ |
@@ -58,2 +58,3 @@ /** | ||
* @property {boolean} [version] Output the version number | ||
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files | ||
* @property {string[]} _ Positional filenames or patterns | ||
@@ -143,2 +144,13 @@ */ | ||
let warnIgnoredFlag; | ||
if (usingFlatConfig) { | ||
warnIgnoredFlag = { | ||
option: "warn-ignored", | ||
type: "Boolean", | ||
default: "true", | ||
description: "Suppress warnings when the file list includes ignored files" | ||
}; | ||
} | ||
return optionator({ | ||
@@ -182,3 +194,3 @@ prepend: "eslint [options] file.js [file.js] [dir]", | ||
{ | ||
heading: "Specifying rules and plugins" | ||
heading: "Specify Rules and Plugins" | ||
}, | ||
@@ -197,3 +209,3 @@ { | ||
{ | ||
heading: "Fixing problems" | ||
heading: "Fix Problems" | ||
}, | ||
@@ -218,3 +230,3 @@ { | ||
{ | ||
heading: "Ignoring files" | ||
heading: "Ignore Files" | ||
}, | ||
@@ -237,3 +249,3 @@ ignorePathFlag, | ||
{ | ||
heading: "Using stdin" | ||
heading: "Use stdin" | ||
}, | ||
@@ -252,3 +264,3 @@ { | ||
{ | ||
heading: "Handling warnings" | ||
heading: "Handle Warnings" | ||
}, | ||
@@ -359,2 +371,3 @@ { | ||
}, | ||
warnIgnoredFlag, | ||
{ | ||
@@ -361,0 +374,0 @@ option: "debug", |
@@ -19,3 +19,5 @@ /** | ||
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"), | ||
{ Linter, SourceCodeFixer, interpolate } = require("../linter"); | ||
{ Linter, SourceCodeFixer, interpolate } = require("../linter"), | ||
CodePath = require("../linter/code-path-analysis/code-path"); | ||
const { FlatConfigArray } = require("../config/flat-config-array"); | ||
@@ -36,4 +38,5 @@ const { defaultConfig } = require("../config/default-config"); | ||
/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ | ||
/** @typedef {import("../shared/types").Rule} Rule */ | ||
/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ | ||
/** | ||
@@ -77,3 +80,2 @@ * A test case that is expected to pass lint. | ||
*/ | ||
/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ | ||
@@ -137,2 +139,11 @@ //------------------------------------------------------------------------------ | ||
const forbiddenMethods = [ | ||
"applyInlineConfig", | ||
"applyLanguageOptions", | ||
"finalize" | ||
]; | ||
/** @type {Map<string,WeakSet>} */ | ||
const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()]))); | ||
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); | ||
@@ -281,2 +292,45 @@ | ||
/** | ||
* Emit a deprecation warning if rule uses CodePath#currentSegments. | ||
* @param {string} ruleName Name of the rule. | ||
* @returns {void} | ||
*/ | ||
function emitCodePathCurrentSegmentsWarning(ruleName) { | ||
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { | ||
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; | ||
process.emitWarning( | ||
`"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, | ||
"DeprecationWarning" | ||
); | ||
} | ||
} | ||
/** | ||
* Function to replace forbidden `SourceCode` methods. Allows just one call per method. | ||
* @param {string} methodName The name of the method to forbid. | ||
* @param {Function} prototype The prototype with the original method to call. | ||
* @returns {Function} The function that throws the error. | ||
*/ | ||
function throwForbiddenMethodError(methodName, prototype) { | ||
const original = prototype[methodName]; | ||
return function(...args) { | ||
const called = forbiddenMethodCalls.get(methodName); | ||
/* eslint-disable no-invalid-this -- needed to operate as a method. */ | ||
if (!called.has(this)) { | ||
called.add(this); | ||
return original.apply(this, args); | ||
} | ||
/* eslint-enable no-invalid-this -- not needed past this point */ | ||
throw new Error( | ||
`\`SourceCode#${methodName}()\` cannot be called inside a rule.` | ||
); | ||
}; | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -353,3 +407,3 @@ // Public Interface | ||
static setDefaultConfig(config) { | ||
if (typeof config !== "object") { | ||
if (typeof config !== "object" || config === null) { | ||
throw new TypeError("FlatRuleTester.setDefaultConfig: config must be an object"); | ||
@@ -439,3 +493,3 @@ } | ||
"Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + | ||
"See https://eslint.org/docs/developer-guide/nodejs-api#customizing-ruletester for more." | ||
"See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." | ||
); | ||
@@ -457,3 +511,3 @@ } | ||
* @param {string} ruleName The name of the rule to run. | ||
* @param {Function} rule The rule to test. | ||
* @param {Function | Rule} rule The rule to test. | ||
* @param {{ | ||
@@ -492,2 +546,3 @@ * valid: (ValidTestCase | string)[], | ||
const baseConfig = [ | ||
{ files: ["**"] }, // Make sure the default config matches for all files | ||
{ | ||
@@ -631,11 +686,13 @@ plugins: { | ||
rules: { | ||
"validate-ast"() { | ||
return { | ||
Program(node) { | ||
beforeAST = cloneDeeplyExcludesParent(node); | ||
}, | ||
"Program:exit"(node) { | ||
afterAST = node; | ||
} | ||
}; | ||
"validate-ast": { | ||
create() { | ||
return { | ||
Program(node) { | ||
beforeAST = cloneDeeplyExcludesParent(node); | ||
}, | ||
"Program:exit"(node) { | ||
afterAST = node; | ||
} | ||
}; | ||
} | ||
} | ||
@@ -673,6 +730,2 @@ } | ||
// Verify the code. | ||
const { getComments } = SourceCode.prototype; | ||
let messages; | ||
// check for validation errors | ||
@@ -687,9 +740,30 @@ try { | ||
// Verify the code. | ||
const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; | ||
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); | ||
let messages; | ||
try { | ||
SourceCode.prototype.getComments = getCommentsDeprecation; | ||
Object.defineProperty(CodePath.prototype, "currentSegments", { | ||
get() { | ||
emitCodePathCurrentSegmentsWarning(ruleName); | ||
return originalCurrentSegments.get.call(this); | ||
} | ||
}); | ||
forbiddenMethods.forEach(methodName => { | ||
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype); | ||
}); | ||
messages = linter.verify(code, configs, filename); | ||
} finally { | ||
SourceCode.prototype.getComments = getComments; | ||
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); | ||
SourceCode.prototype.applyInlineConfig = applyInlineConfig; | ||
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; | ||
SourceCode.prototype.finalize = finalize; | ||
} | ||
const fatalErrorMessage = messages.find(m => m.fatal); | ||
@@ -1025,25 +1099,31 @@ | ||
* one of the templates above. | ||
* The test suites for valid/invalid are created conditionally as | ||
* test runners (eg. vitest) fail for empty test suites. | ||
*/ | ||
this.constructor.describe(ruleName, () => { | ||
this.constructor.describe("valid", () => { | ||
test.valid.forEach(valid => { | ||
this.constructor[valid.only ? "itOnly" : "it"]( | ||
sanitize(typeof valid === "object" ? valid.name || valid.code : valid), | ||
() => { | ||
testValidTemplate(valid); | ||
} | ||
); | ||
if (test.valid.length > 0) { | ||
this.constructor.describe("valid", () => { | ||
test.valid.forEach(valid => { | ||
this.constructor[valid.only ? "itOnly" : "it"]( | ||
sanitize(typeof valid === "object" ? valid.name || valid.code : valid), | ||
() => { | ||
testValidTemplate(valid); | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
} | ||
this.constructor.describe("invalid", () => { | ||
test.invalid.forEach(invalid => { | ||
this.constructor[invalid.only ? "itOnly" : "it"]( | ||
sanitize(invalid.name || invalid.code), | ||
() => { | ||
testInvalidTemplate(invalid); | ||
} | ||
); | ||
if (test.invalid.length > 0) { | ||
this.constructor.describe("invalid", () => { | ||
test.invalid.forEach(invalid => { | ||
this.constructor[invalid.only ? "itOnly" : "it"]( | ||
sanitize(invalid.name || invalid.code), | ||
() => { | ||
testInvalidTemplate(invalid); | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); | ||
@@ -1050,0 +1130,0 @@ } |
@@ -51,3 +51,4 @@ /** | ||
{ getRuleOptionsSchema, validate } = require("../shared/config-validator"), | ||
{ Linter, SourceCodeFixer, interpolate } = require("../linter"); | ||
{ Linter, SourceCodeFixer, interpolate } = require("../linter"), | ||
CodePath = require("../linter/code-path-analysis/code-path"); | ||
@@ -66,4 +67,5 @@ const ajv = require("../shared/ajv")({ strictDefaults: true }); | ||
/** @typedef {import("../shared/types").Parser} Parser */ | ||
/** @typedef {import("../shared/types").Rule} Rule */ | ||
/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ | ||
/** | ||
@@ -113,3 +115,2 @@ * A test case that is expected to pass lint. | ||
*/ | ||
/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ | ||
@@ -168,4 +169,39 @@ //------------------------------------------------------------------------------ | ||
const forbiddenMethods = [ | ||
"applyInlineConfig", | ||
"applyLanguageOptions", | ||
"finalize" | ||
]; | ||
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); | ||
const DEPRECATED_SOURCECODE_PASSTHROUGHS = { | ||
getSource: "getText", | ||
getSourceLines: "getLines", | ||
getAllComments: "getAllComments", | ||
getNodeByRangeIndex: "getNodeByRangeIndex", | ||
// getComments: "getComments", -- already handled by a separate error | ||
getCommentsBefore: "getCommentsBefore", | ||
getCommentsAfter: "getCommentsAfter", | ||
getCommentsInside: "getCommentsInside", | ||
getJSDocComment: "getJSDocComment", | ||
getFirstToken: "getFirstToken", | ||
getFirstTokens: "getFirstTokens", | ||
getLastToken: "getLastToken", | ||
getLastTokens: "getLastTokens", | ||
getTokenAfter: "getTokenAfter", | ||
getTokenBefore: "getTokenBefore", | ||
getTokenByRangeStart: "getTokenByRangeStart", | ||
getTokens: "getTokens", | ||
getTokensAfter: "getTokensAfter", | ||
getTokensBefore: "getTokensBefore", | ||
getTokensBetween: "getTokensBetween", | ||
getScope: "getScope", | ||
getAncestors: "getAncestors", | ||
getDeclaredVariables: "getDeclaredVariables", | ||
markVariableAsUsed: "markVariableAsUsed" | ||
}; | ||
/** | ||
@@ -313,2 +349,15 @@ * Clones a given value deeply. | ||
/** | ||
* Function to replace forbidden `SourceCode` methods. | ||
* @param {string} methodName The name of the method to forbid. | ||
* @returns {Function} The function that throws the error. | ||
*/ | ||
function throwForbiddenMethodError(methodName) { | ||
return () => { | ||
throw new Error( | ||
`\`SourceCode#${methodName}()\` cannot be called inside a rule.` | ||
); | ||
}; | ||
} | ||
/** | ||
* Emit a deprecation warning if function-style format is being used. | ||
@@ -322,3 +371,3 @@ * @param {string} ruleName Name of the rule. | ||
process.emitWarning( | ||
`"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/developer-guide/working-with-rules`, | ||
`"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules`, | ||
"DeprecationWarning" | ||
@@ -338,3 +387,3 @@ ); | ||
process.emitWarning( | ||
`"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas`, | ||
`"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas`, | ||
"DeprecationWarning" | ||
@@ -345,2 +394,49 @@ ); | ||
/** | ||
* Emit a deprecation warning if a rule uses a deprecated `context` method. | ||
* @param {string} ruleName Name of the rule. | ||
* @param {string} methodName The name of the method on `context` that was used. | ||
* @returns {void} | ||
*/ | ||
function emitDeprecatedContextMethodWarning(ruleName, methodName) { | ||
if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) { | ||
emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true; | ||
process.emitWarning( | ||
`"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`, | ||
"DeprecationWarning" | ||
); | ||
} | ||
} | ||
/** | ||
* Emit a deprecation warning if rule uses CodePath#currentSegments. | ||
* @param {string} ruleName Name of the rule. | ||
* @returns {void} | ||
*/ | ||
function emitCodePathCurrentSegmentsWarning(ruleName) { | ||
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { | ||
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; | ||
process.emitWarning( | ||
`"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, | ||
"DeprecationWarning" | ||
); | ||
} | ||
} | ||
/** | ||
* Emit a deprecation warning if `context.parserServices` is used. | ||
* @param {string} ruleName Name of the rule. | ||
* @returns {void} | ||
*/ | ||
function emitParserServicesWarning(ruleName) { | ||
if (!emitParserServicesWarning[`warned-${ruleName}`]) { | ||
emitParserServicesWarning[`warned-${ruleName}`] = true; | ||
process.emitWarning( | ||
`"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`, | ||
"DeprecationWarning" | ||
); | ||
} | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -423,3 +519,3 @@ // Public Interface | ||
static setDefaultConfig(config) { | ||
if (typeof config !== "object") { | ||
if (typeof config !== "object" || config === null) { | ||
throw new TypeError("RuleTester.setDefaultConfig: config must be an object"); | ||
@@ -505,3 +601,3 @@ } | ||
"Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + | ||
"See https://eslint.org/docs/developer-guide/nodejs-api#customizing-ruletester for more." | ||
"See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." | ||
); | ||
@@ -522,6 +618,9 @@ } | ||
* @param {string} name The name of the rule to define. | ||
* @param {Function} rule The rule definition. | ||
* @param {Function | Rule} rule The rule definition. | ||
* @returns {void} | ||
*/ | ||
defineRule(name, rule) { | ||
if (typeof rule === "function") { | ||
emitLegacyRuleAPIWarning(name); | ||
} | ||
this.rules[name] = rule; | ||
@@ -533,3 +632,3 @@ } | ||
* @param {string} ruleName The name of the rule to run. | ||
* @param {Function} rule The rule to test. | ||
* @param {Function | Rule} rule The rule to test. | ||
* @param {{ | ||
@@ -578,3 +677,34 @@ * valid: (ValidTestCase | string)[], | ||
return (typeof rule === "function" ? rule : rule.create)(context); | ||
// wrap all deprecated methods | ||
const newContext = Object.create( | ||
context, | ||
Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [ | ||
methodName, | ||
{ | ||
value(...args) { | ||
// emit deprecation warning | ||
emitDeprecatedContextMethodWarning(ruleName, methodName); | ||
// call the original method | ||
return context[methodName].call(this, ...args); | ||
}, | ||
enumerable: true | ||
} | ||
])) | ||
); | ||
// emit warning about context.parserServices | ||
const parserServices = context.parserServices; | ||
Object.defineProperty(newContext, "parserServices", { | ||
get() { | ||
emitParserServicesWarning(ruleName); | ||
return parserServices; | ||
} | ||
}); | ||
Object.freeze(newContext); | ||
return (typeof rule === "function" ? rule : rule.create)(newContext); | ||
} | ||
@@ -648,10 +778,14 @@ })); | ||
*/ | ||
linter.defineRule("rule-tester/validate-ast", () => ({ | ||
Program(node) { | ||
beforeAST = cloneDeeplyExcludesParent(node); | ||
}, | ||
"Program:exit"(node) { | ||
afterAST = node; | ||
linter.defineRule("rule-tester/validate-ast", { | ||
create() { | ||
return { | ||
Program(node) { | ||
beforeAST = cloneDeeplyExcludesParent(node); | ||
}, | ||
"Program:exit"(node) { | ||
afterAST = node; | ||
} | ||
}; | ||
} | ||
})); | ||
}); | ||
@@ -695,3 +829,4 @@ if (typeof config.parser === "string") { | ||
// Verify the code. | ||
const { getComments } = SourceCode.prototype; | ||
const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; | ||
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); | ||
let messages; | ||
@@ -701,5 +836,20 @@ | ||
SourceCode.prototype.getComments = getCommentsDeprecation; | ||
Object.defineProperty(CodePath.prototype, "currentSegments", { | ||
get() { | ||
emitCodePathCurrentSegmentsWarning(ruleName); | ||
return originalCurrentSegments.get.call(this); | ||
} | ||
}); | ||
forbiddenMethods.forEach(methodName => { | ||
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName); | ||
}); | ||
messages = linter.verify(code, config, filename); | ||
} finally { | ||
SourceCode.prototype.getComments = getComments; | ||
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); | ||
SourceCode.prototype.applyInlineConfig = applyInlineConfig; | ||
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; | ||
SourceCode.prototype.finalize = finalize; | ||
} | ||
@@ -1037,25 +1187,31 @@ | ||
* one of the templates above. | ||
* The test suites for valid/invalid are created conditionally as | ||
* test runners (eg. vitest) fail for empty test suites. | ||
*/ | ||
this.constructor.describe(ruleName, () => { | ||
this.constructor.describe("valid", () => { | ||
test.valid.forEach(valid => { | ||
this.constructor[valid.only ? "itOnly" : "it"]( | ||
sanitize(typeof valid === "object" ? valid.name || valid.code : valid), | ||
() => { | ||
testValidTemplate(valid); | ||
} | ||
); | ||
if (test.valid.length > 0) { | ||
this.constructor.describe("valid", () => { | ||
test.valid.forEach(valid => { | ||
this.constructor[valid.only ? "itOnly" : "it"]( | ||
sanitize(typeof valid === "object" ? valid.name || valid.code : valid), | ||
() => { | ||
testValidTemplate(valid); | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
} | ||
this.constructor.describe("invalid", () => { | ||
test.invalid.forEach(invalid => { | ||
this.constructor[invalid.only ? "itOnly" : "it"]( | ||
sanitize(invalid.name || invalid.code), | ||
() => { | ||
testInvalidTemplate(invalid); | ||
} | ||
); | ||
if (test.invalid.length > 0) { | ||
this.constructor.describe("invalid", () => { | ||
test.invalid.forEach(invalid => { | ||
this.constructor[invalid.only ? "itOnly" : "it"]( | ||
sanitize(invalid.name || invalid.code), | ||
() => { | ||
testInvalidTemplate(invalid); | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); | ||
@@ -1062,0 +1218,0 @@ } |
@@ -145,3 +145,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/accessor-pairs" | ||
url: "https://eslint.org/docs/latest/rules/accessor-pairs" | ||
}, | ||
@@ -182,3 +182,3 @@ | ||
const enforceForClassMembers = config.enforceForClassMembers !== false; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -229,49 +229,41 @@ /** | ||
/** | ||
* Creates a new `AccessorData` object for the given getter or setter node. | ||
* @param {ASTNode} node A getter or setter node. | ||
* @returns {AccessorData} New `AccessorData` object that contains the given node. | ||
* Checks accessor pairs in the given list of nodes. | ||
* @param {ASTNode[]} nodes The list to check. | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function createAccessorData(node) { | ||
const name = astUtils.getStaticPropertyName(node); | ||
const key = (name !== null) ? name : sourceCode.getTokens(node.key); | ||
function checkList(nodes) { | ||
const accessors = []; | ||
let found = false; | ||
return { | ||
key, | ||
getters: node.kind === "get" ? [node] : [], | ||
setters: node.kind === "set" ? [node] : [] | ||
}; | ||
} | ||
for (let i = 0; i < nodes.length; i++) { | ||
const node = nodes[i]; | ||
/** | ||
* Merges the given `AccessorData` object into the given accessors list. | ||
* @param {AccessorData[]} accessors The list to merge into. | ||
* @param {AccessorData} accessorData The object to merge. | ||
* @returns {AccessorData[]} The same instance with the merged object. | ||
* @private | ||
*/ | ||
function mergeAccessorData(accessors, accessorData) { | ||
const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); | ||
if (isAccessorKind(node)) { | ||
if (equalKeyElement) { | ||
equalKeyElement.getters.push(...accessorData.getters); | ||
equalKeyElement.setters.push(...accessorData.setters); | ||
} else { | ||
accessors.push(accessorData); | ||
} | ||
// Creates a new `AccessorData` object for the given getter or setter node. | ||
const name = astUtils.getStaticPropertyName(node); | ||
const key = (name !== null) ? name : sourceCode.getTokens(node.key); | ||
return accessors; | ||
} | ||
// Merges the given `AccessorData` object into the given accessors list. | ||
for (let j = 0; j < accessors.length; j++) { | ||
const accessor = accessors[j]; | ||
/** | ||
* Checks accessor pairs in the given list of nodes. | ||
* @param {ASTNode[]} nodes The list to check. | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function checkList(nodes) { | ||
const accessors = nodes | ||
.filter(isAccessorKind) | ||
.map(createAccessorData) | ||
.reduce(mergeAccessorData, []); | ||
if (areEqualKeys(accessor.key, key)) { | ||
accessor.getters.push(...node.kind === "get" ? [node] : []); | ||
accessor.setters.push(...node.kind === "set" ? [node] : []); | ||
found = true; | ||
break; | ||
} | ||
} | ||
if (!found) { | ||
accessors.push({ | ||
key, | ||
getters: node.kind === "get" ? [node] : [], | ||
setters: node.kind === "set" ? [node] : [] | ||
}); | ||
} | ||
found = false; | ||
} | ||
} | ||
@@ -278,0 +270,0 @@ for (const { getters, setters } of accessors) { |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/array-bracket-newline" | ||
url: "https://eslint.org/docs/latest/rules/array-bracket-newline" | ||
}, | ||
@@ -60,3 +60,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -63,0 +63,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/array-bracket-spacing" | ||
url: "https://eslint.org/docs/latest/rules/array-bracket-spacing" | ||
}, | ||
@@ -57,3 +57,3 @@ | ||
const spaced = context.options[0] === "always", | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -60,0 +60,0 @@ /** |
@@ -19,14 +19,5 @@ /** | ||
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; | ||
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u; | ||
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u; | ||
/** | ||
* Checks a given code path segment is reachable. | ||
* @param {CodePathSegment} segment A segment to check. | ||
* @returns {boolean} `true` if the segment is reachable. | ||
*/ | ||
function isReachable(segment) { | ||
return segment.reachable; | ||
} | ||
/** | ||
* Checks a given node is a member access which has the specified name's | ||
@@ -43,2 +34,18 @@ * property. | ||
/** | ||
* Checks all segments in a set and returns true if any are reachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if any segment is reachable; false otherwise. | ||
*/ | ||
function isAnySegmentReachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Returns a human-legible description of an array method | ||
@@ -134,2 +141,72 @@ * @param {string} arrayMethodName A method name to fully qualify | ||
/** | ||
* Checks if the given node is a void expression. | ||
* @param {ASTNode} node The node to check. | ||
* @returns {boolean} - `true` if the node is a void expression | ||
*/ | ||
function isExpressionVoid(node) { | ||
return node.type === "UnaryExpression" && node.operator === "void"; | ||
} | ||
/** | ||
* Fixes the linting error by prepending "void " to the given node | ||
* @param {Object} sourceCode context given by context.sourceCode | ||
* @param {ASTNode} node The node to fix. | ||
* @param {Object} fixer The fixer object provided by ESLint. | ||
* @returns {Array<Object>} - An array of fix objects to apply to the node. | ||
*/ | ||
function voidPrependFixer(sourceCode, node, fixer) { | ||
const requiresParens = | ||
// prepending `void ` will fail if the node has a lower precedence than void | ||
astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) && | ||
// check if there are parentheses around the node to avoid redundant parentheses | ||
!astUtils.isParenthesised(sourceCode, node); | ||
// avoid parentheses issues | ||
const returnOrArrowToken = sourceCode.getTokenBefore( | ||
node, | ||
node.parent.type === "ArrowFunctionExpression" | ||
? astUtils.isArrowToken | ||
// isReturnToken | ||
: token => token.type === "Keyword" && token.value === "return" | ||
); | ||
const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); | ||
const prependSpace = | ||
// is return token, as => allows void to be adjacent | ||
returnOrArrowToken.value === "return" && | ||
// If two tokens (return and "(") are adjacent | ||
returnOrArrowToken.range[1] === firstToken.range[0]; | ||
return [ | ||
fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`), | ||
fixer.insertTextAfter(node, requiresParens ? ")" : "") | ||
]; | ||
} | ||
/** | ||
* Fixes the linting error by `wrapping {}` around the given node's body. | ||
* @param {Object} sourceCode context given by context.sourceCode | ||
* @param {ASTNode} node The node to fix. | ||
* @param {Object} fixer The fixer object provided by ESLint. | ||
* @returns {Array<Object>} - An array of fix objects to apply to the node. | ||
*/ | ||
function curlyWrapFixer(sourceCode, node, fixer) { | ||
const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); | ||
const firstToken = sourceCode.getTokenAfter(arrowToken); | ||
const lastToken = sourceCode.getLastToken(node); | ||
return [ | ||
fixer.insertTextBefore(firstToken, "{"), | ||
fixer.insertTextAfter(lastToken, "}") | ||
]; | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -147,5 +224,8 @@ // Rule Definition | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/array-callback-return" | ||
url: "https://eslint.org/docs/latest/rules/array-callback-return" | ||
}, | ||
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive | ||
hasSuggestions: true, | ||
schema: [ | ||
@@ -162,2 +242,6 @@ { | ||
default: false | ||
}, | ||
allowVoid: { | ||
type: "boolean", | ||
default: false | ||
} | ||
@@ -173,3 +257,5 @@ }, | ||
expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.", | ||
expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}." | ||
expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.", | ||
wrapBraces: "Wrap the expression in `{}`.", | ||
prependVoid: "Prepend `void` to the expression." | ||
} | ||
@@ -180,4 +266,4 @@ }, | ||
const options = context.options[0] || { allowImplicit: false, checkForEach: false }; | ||
const sourceCode = context.getSourceCode(); | ||
const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false }; | ||
const sourceCode = context.sourceCode; | ||
@@ -208,15 +294,44 @@ let funcInfo = { | ||
let messageId = null; | ||
const messageAndSuggestions = { messageId: "", suggest: [] }; | ||
if (funcInfo.arrayMethodName === "forEach") { | ||
if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) { | ||
messageId = "expectedNoReturnValue"; | ||
if (options.allowVoid) { | ||
if (isExpressionVoid(node.body)) { | ||
return; | ||
} | ||
messageAndSuggestions.messageId = "expectedNoReturnValue"; | ||
messageAndSuggestions.suggest = [ | ||
{ | ||
messageId: "wrapBraces", | ||
fix(fixer) { | ||
return curlyWrapFixer(sourceCode, node, fixer); | ||
} | ||
}, | ||
{ | ||
messageId: "prependVoid", | ||
fix(fixer) { | ||
return voidPrependFixer(sourceCode, node.body, fixer); | ||
} | ||
} | ||
]; | ||
} else { | ||
messageAndSuggestions.messageId = "expectedNoReturnValue"; | ||
messageAndSuggestions.suggest = [{ | ||
messageId: "wrapBraces", | ||
fix(fixer) { | ||
return curlyWrapFixer(sourceCode, node, fixer); | ||
} | ||
}]; | ||
} | ||
} | ||
} else { | ||
if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) { | ||
messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside"; | ||
if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) { | ||
messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside"; | ||
} | ||
} | ||
if (messageId) { | ||
if (messageAndSuggestions.messageId) { | ||
const name = astUtils.getFunctionNameWithKind(node); | ||
@@ -227,4 +342,5 @@ | ||
loc: astUtils.getFunctionHeadLoc(node, sourceCode), | ||
messageId, | ||
data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) } | ||
messageId: messageAndSuggestions.messageId, | ||
data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }, | ||
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null | ||
}); | ||
@@ -254,3 +370,4 @@ } | ||
!node.generator, | ||
node | ||
node, | ||
currentSegments: new Set() | ||
}; | ||
@@ -264,2 +381,19 @@ }, | ||
onUnreachableCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
onCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
// Checks the return statement is valid. | ||
@@ -274,3 +408,3 @@ ReturnStatement(node) { | ||
let messageId = null; | ||
const messageAndSuggestions = { messageId: "", suggest: [] }; | ||
@@ -281,3 +415,18 @@ if (funcInfo.arrayMethodName === "forEach") { | ||
if (options.checkForEach && node.argument) { | ||
messageId = "expectedNoReturnValue"; | ||
if (options.allowVoid) { | ||
if (isExpressionVoid(node.argument)) { | ||
return; | ||
} | ||
messageAndSuggestions.messageId = "expectedNoReturnValue"; | ||
messageAndSuggestions.suggest = [{ | ||
messageId: "prependVoid", | ||
fix(fixer) { | ||
return voidPrependFixer(sourceCode, node.argument, fixer); | ||
} | ||
}]; | ||
} else { | ||
messageAndSuggestions.messageId = "expectedNoReturnValue"; | ||
} | ||
} | ||
@@ -288,14 +437,15 @@ } else { | ||
if (!options.allowImplicit && !node.argument) { | ||
messageId = "expectedReturnValue"; | ||
messageAndSuggestions.messageId = "expectedReturnValue"; | ||
} | ||
} | ||
if (messageId) { | ||
if (messageAndSuggestions.messageId) { | ||
context.report({ | ||
node, | ||
messageId, | ||
messageId: messageAndSuggestions.messageId, | ||
data: { | ||
name: astUtils.getFunctionNameWithKind(funcInfo.node), | ||
arrayMethodName: fullMethodName(funcInfo.arrayMethodName) | ||
} | ||
}, | ||
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null | ||
}); | ||
@@ -302,0 +452,0 @@ } |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/array-element-newline" | ||
url: "https://eslint.org/docs/latest/rules/array-element-newline" | ||
}, | ||
@@ -51,2 +51,3 @@ | ||
}, | ||
type: "array", | ||
items: [ | ||
@@ -83,3 +84,3 @@ { | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -245,7 +246,11 @@ //---------------------------------------------------------------------- | ||
const linebreaksCount = node.elements.map((element, i) => { | ||
let linebreaksCount = 0; | ||
for (let i = 0; i < node.elements.length; i++) { | ||
const element = node.elements[i]; | ||
const previousElement = elements[i - 1]; | ||
if (i === 0 || element === null || previousElement === null) { | ||
return false; | ||
continue; | ||
} | ||
@@ -257,4 +262,6 @@ | ||
return !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement); | ||
}).filter(isBreak => isBreak === true).length; | ||
if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { | ||
linebreaksCount++; | ||
} | ||
} | ||
@@ -261,0 +268,0 @@ const needsLinebreaks = ( |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/arrow-body-style" | ||
url: "https://eslint.org/docs/latest/rules/arrow-body-style" | ||
}, | ||
@@ -78,3 +78,3 @@ | ||
const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let funcInfo = null; | ||
@@ -81,0 +81,0 @@ |
@@ -38,3 +38,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/arrow-parens" | ||
url: "https://eslint.org/docs/latest/rules/arrow-parens" | ||
}, | ||
@@ -73,3 +73,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -76,0 +76,0 @@ /** |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/arrow-spacing" | ||
url: "https://eslint.org/docs/latest/rules/arrow-spacing" | ||
}, | ||
@@ -65,3 +65,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -68,0 +68,0 @@ /** |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/block-scoped-var" | ||
url: "https://eslint.org/docs/latest/rules/block-scoped-var" | ||
}, | ||
@@ -26,3 +26,3 @@ | ||
messages: { | ||
outOfScope: "'{{name}}' used outside of binding context." | ||
outOfScope: "'{{name}}' declared on line {{definitionLine}} column {{definitionColumn}} is used outside of binding context." | ||
} | ||
@@ -33,2 +33,3 @@ }, | ||
let stack = []; | ||
const sourceCode = context.sourceCode; | ||
@@ -55,8 +56,18 @@ /** | ||
* @param {eslint-scope.Reference} reference A reference to report. | ||
* @param {eslint-scope.Definition} definition A definition for which to report reference. | ||
* @returns {void} | ||
*/ | ||
function report(reference) { | ||
function report(reference, definition) { | ||
const identifier = reference.identifier; | ||
const definitionPosition = definition.name.loc.start; | ||
context.report({ node: identifier, messageId: "outOfScope", data: { name: identifier.name } }); | ||
context.report({ | ||
node: identifier, | ||
messageId: "outOfScope", | ||
data: { | ||
name: identifier.name, | ||
definitionLine: definitionPosition.line, | ||
definitionColumn: definitionPosition.column + 1 | ||
} | ||
}); | ||
} | ||
@@ -90,3 +101,3 @@ | ||
// Gets declared variables, and checks its references. | ||
const variables = context.getDeclaredVariables(node); | ||
const variables = sourceCode.getDeclaredVariables(node); | ||
@@ -99,3 +110,3 @@ for (let i = 0; i < variables.length; ++i) { | ||
.filter(isOutsideOfScope) | ||
.forEach(report); | ||
.forEach(ref => report(ref, variables[i].defs.find(def => def.parent === node))); | ||
} | ||
@@ -102,0 +113,0 @@ } |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/block-spacing" | ||
url: "https://eslint.org/docs/latest/rules/block-spacing" | ||
}, | ||
@@ -41,3 +41,3 @@ | ||
messageId = always ? "missing" : "extra", | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -44,0 +44,0 @@ /** |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/brace-style" | ||
url: "https://eslint.org/docs/latest/rules/brace-style" | ||
}, | ||
@@ -57,3 +57,3 @@ | ||
params = context.options[1] || {}, | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -60,0 +60,0 @@ //-------------------------------------------------------------------------- |
@@ -24,3 +24,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/callback-return" | ||
url: "https://eslint.org/docs/latest/rules/callback-return" | ||
}, | ||
@@ -41,3 +41,3 @@ | ||
const callbacks = context.options[0] || ["callback", "cb", "next"], | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -44,0 +44,0 @@ //-------------------------------------------------------------------------- |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/camelcase" | ||
url: "https://eslint.org/docs/latest/rules/camelcase" | ||
}, | ||
@@ -77,2 +77,3 @@ | ||
const allow = options.allow || []; | ||
const sourceCode = context.sourceCode; | ||
@@ -250,4 +251,4 @@ //-------------------------------------------------------------------------- | ||
// Report camelcase of global variable references ------------------ | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
@@ -301,3 +302,3 @@ if (!ignoreGlobals) { | ||
]](node) { | ||
for (const variable of context.getDeclaredVariables(node)) { | ||
for (const variable of sourceCode.getDeclaredVariables(node)) { | ||
if (isGoodName(variable.name)) { | ||
@@ -352,3 +353,3 @@ continue; | ||
ImportDeclaration(node) { | ||
for (const variable of context.getDeclaredVariables(node)) { | ||
for (const variable of sourceCode.getDeclaredVariables(node)) { | ||
if (isGoodName(variable.name)) { | ||
@@ -355,0 +356,0 @@ continue; |
@@ -110,3 +110,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/capitalized-comments" | ||
url: "https://eslint.org/docs/latest/rules/capitalized-comments" | ||
}, | ||
@@ -143,3 +143,3 @@ | ||
normalizedOptions = getAllNormalizedOptions(context.options[1]), | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -146,0 +146,0 @@ createRegExpForIgnorePatterns(normalizedOptions); |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/class-methods-use-this" | ||
url: "https://eslint.org/docs/latest/rules/class-methods-use-this" | ||
}, | ||
@@ -137,3 +137,3 @@ | ||
node, | ||
loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()), | ||
loc: astUtils.getFunctionHeadLoc(node, context.sourceCode), | ||
messageId: "missingThis", | ||
@@ -140,0 +140,0 @@ data: { |
@@ -81,3 +81,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/comma-dangle" | ||
url: "https://eslint.org/docs/latest/rules/comma-dangle" | ||
}, | ||
@@ -140,3 +140,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -351,3 +351,3 @@ /** | ||
never: forbidTrailingComma, | ||
ignore: () => {} | ||
ignore() {} | ||
}; | ||
@@ -354,0 +354,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/comma-spacing" | ||
url: "https://eslint.org/docs/latest/rules/comma-spacing" | ||
}, | ||
@@ -52,3 +52,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const tokensAndComments = sourceCode.tokensAndComments; | ||
@@ -55,0 +55,0 @@ |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/comma-style" | ||
url: "https://eslint.org/docs/latest/rules/comma-style" | ||
}, | ||
@@ -55,3 +55,3 @@ | ||
const style = context.options[0] || "last", | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
const exceptions = { | ||
@@ -58,0 +58,0 @@ ArrayPattern: true, |
@@ -28,3 +28,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/complexity" | ||
url: "https://eslint.org/docs/latest/rules/complexity" | ||
}, | ||
@@ -31,0 +31,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/computed-property-spacing" | ||
url: "https://eslint.org/docs/latest/rules/computed-property-spacing" | ||
}, | ||
@@ -53,3 +53,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" | ||
@@ -56,0 +56,0 @@ const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers; |
@@ -19,8 +19,15 @@ /** | ||
/** | ||
* Checks whether or not a given code path segment is unreachable. | ||
* @param {CodePathSegment} segment A CodePathSegment to check. | ||
* @returns {boolean} `true` if the segment is unreachable. | ||
* Checks all segments in a set and returns true if all are unreachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if all segments are unreachable; false otherwise. | ||
*/ | ||
function isUnreachable(segment) { | ||
return !segment.reachable; | ||
function areAllSegmentsUnreachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
@@ -52,3 +59,3 @@ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/consistent-return" | ||
url: "https://eslint.org/docs/latest/rules/consistent-return" | ||
}, | ||
@@ -93,3 +100,3 @@ | ||
if (!funcInfo.hasReturnValue || | ||
funcInfo.codePath.currentSegments.every(isUnreachable) || | ||
areAllSegmentsUnreachable(funcInfo.currentSegments) || | ||
astUtils.isES5Constructor(node) || | ||
@@ -110,3 +117,3 @@ isClassConstructor(node) | ||
// `=>` token | ||
loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc; | ||
loc = context.sourceCode.getTokenBefore(node.body, astUtils.isArrowToken).loc; | ||
} else if ( | ||
@@ -122,3 +129,3 @@ node.parent.type === "MethodDefinition" || | ||
// Function name or `function` keyword. | ||
loc = (node.id || context.getSourceCode().getFirstToken(node)).loc; | ||
loc = (node.id || context.sourceCode.getFirstToken(node)).loc; | ||
} | ||
@@ -149,3 +156,4 @@ | ||
messageId: "", | ||
node | ||
node, | ||
currentSegments: new Set() | ||
}; | ||
@@ -157,2 +165,19 @@ }, | ||
onUnreachableCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
onCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
// Reports a given return statement if it's inconsistent. | ||
@@ -159,0 +184,0 @@ ReturnStatement(node) { |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/consistent-this" | ||
url: "https://eslint.org/docs/latest/rules/consistent-this" | ||
}, | ||
@@ -40,2 +40,3 @@ | ||
let aliases = []; | ||
const sourceCode = context.sourceCode; | ||
@@ -120,6 +121,7 @@ if (context.options.length === 0) { | ||
* Check each alias to ensure that is was assigned to the correct value. | ||
* @param {ASTNode} node The node that represents the scope to check. | ||
* @returns {void} | ||
*/ | ||
function ensureWasAssigned() { | ||
const scope = context.getScope(); | ||
function ensureWasAssigned(node) { | ||
const scope = sourceCode.getScope(node); | ||
@@ -126,0 +128,0 @@ aliases.forEach(alias => { |
@@ -13,8 +13,15 @@ /** | ||
/** | ||
* Checks whether a given code path segment is reachable or not. | ||
* @param {CodePathSegment} segment A code path segment to check. | ||
* @returns {boolean} `true` if the segment is reachable. | ||
* Checks all segments in a set and returns true if any are reachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if any segment is reachable; false otherwise. | ||
*/ | ||
function isReachable(segment) { | ||
return segment.reachable; | ||
function isAnySegmentReachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
@@ -128,3 +135,3 @@ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/constructor-super" | ||
url: "https://eslint.org/docs/latest/rules/constructor-super" | ||
}, | ||
@@ -215,3 +222,4 @@ | ||
superIsConstructor: isPossibleConstructor(superClass), | ||
codePath | ||
codePath, | ||
currentSegments: new Set() | ||
}; | ||
@@ -224,3 +232,4 @@ } else { | ||
superIsConstructor: false, | ||
codePath | ||
codePath, | ||
currentSegments: new Set() | ||
}; | ||
@@ -268,2 +277,5 @@ } | ||
onCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { | ||
@@ -289,2 +301,15 @@ return; | ||
onUnreachableCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
/** | ||
@@ -353,8 +378,7 @@ * Update information of the code path segment when a code path was | ||
if (funcInfo.hasExtends) { | ||
const segments = funcInfo.codePath.currentSegments; | ||
const segments = funcInfo.currentSegments; | ||
let duplicate = false; | ||
let info = null; | ||
for (let i = 0; i < segments.length; ++i) { | ||
const segment = segments[i]; | ||
for (const segment of segments) { | ||
@@ -384,3 +408,3 @@ if (segment.reachable) { | ||
} | ||
} else if (funcInfo.codePath.currentSegments.some(isReachable)) { | ||
} else if (isAnySegmentReachable(funcInfo.currentSegments)) { | ||
context.report({ | ||
@@ -409,6 +433,5 @@ messageId: "unexpected", | ||
// Returning argument is a substitute of 'super()'. | ||
const segments = funcInfo.codePath.currentSegments; | ||
const segments = funcInfo.currentSegments; | ||
for (let i = 0; i < segments.length; ++i) { | ||
const segment = segments[i]; | ||
for (const segment of segments) { | ||
@@ -415,0 +438,0 @@ if (segment.reachable) { |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/curly" | ||
url: "https://eslint.org/docs/latest/rules/curly" | ||
}, | ||
@@ -74,3 +74,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -77,0 +77,0 @@ //-------------------------------------------------------------------------- |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/default-case-last" | ||
url: "https://eslint.org/docs/latest/rules/default-case-last" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/default-case" | ||
url: "https://eslint.org/docs/latest/rules/default-case" | ||
}, | ||
@@ -46,3 +46,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -49,0 +49,0 @@ //-------------------------------------------------------------------------- |
@@ -16,3 +16,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/default-param-last" | ||
url: "https://eslint.org/docs/latest/rules/default-param-last" | ||
}, | ||
@@ -19,0 +19,0 @@ |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/dot-location" | ||
url: "https://eslint.org/docs/latest/rules/dot-location" | ||
}, | ||
@@ -47,3 +47,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -50,0 +50,0 @@ /** |
@@ -31,3 +31,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/dot-notation" | ||
url: "https://eslint.org/docs/latest/rules/dot-notation" | ||
}, | ||
@@ -63,3 +63,3 @@ | ||
const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -138,4 +138,3 @@ let allowPattern; | ||
node.computed && | ||
node.property.type === "TemplateLiteral" && | ||
node.property.expressions.length === 0 | ||
astUtils.isStaticTemplateLiteral(node.property) | ||
) { | ||
@@ -142,0 +141,0 @@ checkComputedProperty(node, node.property.quasis[0].value.cooked); |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/eol-last" | ||
url: "https://eslint.org/docs/latest/rules/eol-last" | ||
}, | ||
@@ -44,3 +44,3 @@ | ||
Program: function checkBadEOF(node) { | ||
const sourceCode = context.getSourceCode(), | ||
const sourceCode = context.sourceCode, | ||
src = sourceCode.getText(), | ||
@@ -47,0 +47,0 @@ lastLine = sourceCode.lines[sourceCode.lines.length - 1], |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/eqeqeq" | ||
url: "https://eslint.org/docs/latest/rules/eqeqeq" | ||
}, | ||
@@ -72,3 +72,3 @@ | ||
const options = context.options[1] || {}; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -75,0 +75,0 @@ const nullOption = (config === "always") |
@@ -9,2 +9,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const { getStaticValue } = require("@eslint-community/eslint-utils"); | ||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -21,3 +27,3 @@ //------------------------------------------------------------------------------ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/for-direction" | ||
url: "https://eslint.org/docs/latest/rules/for-direction" | ||
}, | ||
@@ -34,2 +40,3 @@ | ||
create(context) { | ||
const { sourceCode } = context; | ||
@@ -52,13 +59,13 @@ /** | ||
* @param {int} dir expected direction that could either be turned around or invalidated | ||
* @returns {int} return dir, the negated dir or zero if it's not clear for identifiers | ||
* @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear | ||
*/ | ||
function getRightDirection(update, dir) { | ||
if (update.right.type === "UnaryExpression") { | ||
if (update.right.operator === "-") { | ||
return -dir; | ||
} | ||
} else if (update.right.type === "Identifier") { | ||
return 0; | ||
const staticValue = getStaticValue(update.right, sourceCode.getScope(update)); | ||
if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) { | ||
const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0 | ||
return dir * sign; | ||
} | ||
return dir; | ||
return 0; | ||
} | ||
@@ -65,0 +72,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/func-call-spacing" | ||
url: "https://eslint.org/docs/latest/rules/func-call-spacing" | ||
}, | ||
@@ -77,3 +77,3 @@ | ||
const allowNewlines = !never && context.options[1] && context.options[1].allowNewlines; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const text = sourceCode.getText(); | ||
@@ -80,0 +80,0 @@ |
@@ -79,3 +79,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/func-name-matching" | ||
url: "https://eslint.org/docs/latest/rules/func-name-matching" | ||
}, | ||
@@ -82,0 +82,0 @@ |
@@ -35,3 +35,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/func-names" | ||
url: "https://eslint.org/docs/latest/rules/func-names" | ||
}, | ||
@@ -73,3 +73,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -164,3 +164,3 @@ /** | ||
// Skip recursive functions. | ||
const nameVar = context.getDeclaredVariables(node)[0]; | ||
const nameVar = sourceCode.getDeclaredVariables(node)[0]; | ||
@@ -167,0 +167,0 @@ if (isFunctionName(nameVar) && nameVar.references.length > 0) { |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/func-style" | ||
url: "https://eslint.org/docs/latest/rules/func-style" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/function-call-argument-newline" | ||
url: "https://eslint.org/docs/latest/rules/function-call-argument-newline" | ||
}, | ||
@@ -39,3 +39,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -42,0 +42,0 @@ const checkers = { |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/function-paren-newline" | ||
url: "https://eslint.org/docs/latest/rules/function-paren-newline" | ||
}, | ||
@@ -61,3 +61,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const rawOption = context.options[0] || "multiline"; | ||
@@ -64,0 +64,0 @@ const multilineOption = rawOption === "multiline"; |
@@ -36,3 +36,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/generator-star-spacing" | ||
url: "https://eslint.org/docs/latest/rules/generator-star-spacing" | ||
}, | ||
@@ -106,3 +106,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -109,0 +109,0 @@ /** |
@@ -17,11 +17,19 @@ /** | ||
//------------------------------------------------------------------------------ | ||
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; | ||
/** | ||
* Checks a given code path segment is reachable. | ||
* @param {CodePathSegment} segment A segment to check. | ||
* @returns {boolean} `true` if the segment is reachable. | ||
* Checks all segments in a set and returns true if any are reachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if any segment is reachable; false otherwise. | ||
*/ | ||
function isReachable(segment) { | ||
return segment.reachable; | ||
function isAnySegmentReachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
@@ -41,3 +49,3 @@ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/getter-return" | ||
url: "https://eslint.org/docs/latest/rules/getter-return" | ||
}, | ||
@@ -69,3 +77,3 @@ | ||
const options = context.options[0] || { allowImplicit: false }; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -77,3 +85,4 @@ let funcInfo = { | ||
shouldCheck: false, | ||
node: null | ||
node: null, | ||
currentSegments: [] | ||
}; | ||
@@ -92,3 +101,3 @@ | ||
if (funcInfo.shouldCheck && | ||
funcInfo.codePath.currentSegments.some(isReachable) | ||
isAnySegmentReachable(funcInfo.currentSegments) | ||
) { | ||
@@ -152,3 +161,4 @@ context.report({ | ||
shouldCheck: isGetter(node), | ||
node | ||
node, | ||
currentSegments: new Set() | ||
}; | ||
@@ -161,3 +171,18 @@ }, | ||
}, | ||
onUnreachableCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
onCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
// Checks the return statement is valid. | ||
@@ -164,0 +189,0 @@ ReturnStatement(node) { |
@@ -64,3 +64,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/global-require" | ||
url: "https://eslint.org/docs/latest/rules/global-require" | ||
}, | ||
@@ -75,8 +75,10 @@ | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
CallExpression(node) { | ||
const currentScope = context.getScope(); | ||
const currentScope = sourceCode.getScope(node); | ||
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { | ||
const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.has(parent.type)); | ||
const isGoodRequire = sourceCode.getAncestors(node).every(parent => ACCEPTABLE_PARENTS.has(parent.type)); | ||
@@ -83,0 +85,0 @@ if (!isGoodRequire) { |
@@ -101,3 +101,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/grouped-accessor-pairs" | ||
url: "https://eslint.org/docs/latest/rules/grouped-accessor-pairs" | ||
}, | ||
@@ -119,3 +119,3 @@ | ||
const order = context.options[0] || "anyOrder"; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -143,39 +143,2 @@ /** | ||
/** | ||
* Creates a new `AccessorData` object for the given getter or setter node. | ||
* @param {ASTNode} node A getter or setter node. | ||
* @returns {AccessorData} New `AccessorData` object that contains the given node. | ||
* @private | ||
*/ | ||
function createAccessorData(node) { | ||
const name = astUtils.getStaticPropertyName(node); | ||
const key = (name !== null) ? name : sourceCode.getTokens(node.key); | ||
return { | ||
key, | ||
getters: node.kind === "get" ? [node] : [], | ||
setters: node.kind === "set" ? [node] : [] | ||
}; | ||
} | ||
/** | ||
* Merges the given `AccessorData` object into the given accessors list. | ||
* @param {AccessorData[]} accessors The list to merge into. | ||
* @param {AccessorData} accessorData The object to merge. | ||
* @returns {AccessorData[]} The same instance with the merged object. | ||
* @private | ||
*/ | ||
function mergeAccessorData(accessors, accessorData) { | ||
const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); | ||
if (equalKeyElement) { | ||
equalKeyElement.getters.push(...accessorData.getters); | ||
equalKeyElement.setters.push(...accessorData.setters); | ||
} else { | ||
accessors.push(accessorData); | ||
} | ||
return accessors; | ||
} | ||
/** | ||
* Checks accessor pairs in the given list of nodes. | ||
@@ -188,8 +151,36 @@ * @param {ASTNode[]} nodes The list to check. | ||
function checkList(nodes, shouldCheck) { | ||
const accessors = nodes | ||
.filter(shouldCheck) | ||
.filter(isAccessorKind) | ||
.map(createAccessorData) | ||
.reduce(mergeAccessorData, []); | ||
const accessors = []; | ||
let found = false; | ||
for (let i = 0; i < nodes.length; i++) { | ||
const node = nodes[i]; | ||
if (shouldCheck(node) && isAccessorKind(node)) { | ||
// Creates a new `AccessorData` object for the given getter or setter node. | ||
const name = astUtils.getStaticPropertyName(node); | ||
const key = (name !== null) ? name : sourceCode.getTokens(node.key); | ||
// Merges the given `AccessorData` object into the given accessors list. | ||
for (let j = 0; j < accessors.length; j++) { | ||
const accessor = accessors[j]; | ||
if (areEqualKeys(accessor.key, key)) { | ||
accessor.getters.push(...node.kind === "get" ? [node] : []); | ||
accessor.setters.push(...node.kind === "set" ? [node] : []); | ||
found = true; | ||
break; | ||
} | ||
} | ||
if (!found) { | ||
accessors.push({ | ||
key, | ||
getters: node.kind === "get" ? [node] : [], | ||
setters: node.kind === "set" ? [node] : [] | ||
}); | ||
} | ||
found = false; | ||
} | ||
} | ||
for (const { getters, setters } of accessors) { | ||
@@ -196,0 +187,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/guard-for-in" | ||
url: "https://eslint.org/docs/latest/rules/guard-for-in" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/handle-callback-err" | ||
url: "https://eslint.org/docs/latest/rules/handle-callback-err" | ||
}, | ||
@@ -42,2 +42,3 @@ | ||
const errorArgument = context.options[0] || "err"; | ||
const sourceCode = context.sourceCode; | ||
@@ -84,3 +85,3 @@ /** | ||
function checkForError(node) { | ||
const scope = context.getScope(), | ||
const scope = sourceCode.getScope(node), | ||
parameters = getParameters(scope), | ||
@@ -87,0 +88,0 @@ firstParameter = parameters[0]; |
@@ -124,3 +124,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/id-blacklist" | ||
url: "https://eslint.org/docs/latest/rules/id-blacklist" | ||
}, | ||
@@ -144,2 +144,3 @@ | ||
const reportedNodes = new Set(); | ||
const sourceCode = context.sourceCode; | ||
@@ -236,4 +237,4 @@ let globalScope; | ||
Program() { | ||
globalScope = context.getScope(); | ||
Program(node) { | ||
globalScope = sourceCode.getScope(node); | ||
}, | ||
@@ -240,0 +241,0 @@ |
@@ -104,3 +104,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/id-denylist" | ||
url: "https://eslint.org/docs/latest/rules/id-denylist" | ||
}, | ||
@@ -125,2 +125,3 @@ | ||
const reportedNodes = new Set(); | ||
const sourceCode = context.sourceCode; | ||
@@ -215,4 +216,4 @@ let globalScope; | ||
Program() { | ||
globalScope = context.getScope(); | ||
Program(node) { | ||
globalScope = sourceCode.getScope(node); | ||
}, | ||
@@ -219,0 +220,0 @@ |
@@ -12,38 +12,5 @@ /** | ||
//------------------------------------------------------------------------------ | ||
const GraphemeSplitter = require("grapheme-splitter"); | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
const { getGraphemeCount } = require("../shared/string-utils"); | ||
/** | ||
* Checks if the string given as argument is ASCII or not. | ||
* @param {string} value A string that you want to know if it is ASCII or not. | ||
* @returns {boolean} `true` if `value` is ASCII string. | ||
*/ | ||
function isASCII(value) { | ||
if (typeof value !== "string") { | ||
return false; | ||
} | ||
return /^[\u0020-\u007f]*$/u.test(value); | ||
} | ||
/** @type {GraphemeSplitter | undefined} */ | ||
let splitter; | ||
/** | ||
* Gets the length of the string. If the string is not in ASCII, counts graphemes. | ||
* @param {string} value A string that you want to get the length. | ||
* @returns {number} The length of `value`. | ||
*/ | ||
function getStringLength(value) { | ||
if (isASCII(value)) { | ||
return value.length; | ||
} | ||
if (!splitter) { | ||
splitter = new GraphemeSplitter(); | ||
} | ||
return splitter.countGraphemes(value); | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -61,3 +28,3 @@ // Rule Definition | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/id-length" | ||
url: "https://eslint.org/docs/latest/rules/id-length" | ||
}, | ||
@@ -174,3 +141,3 @@ | ||
const nameLength = getStringLength(name); | ||
const nameLength = getGraphemeCount(name); | ||
@@ -177,0 +144,0 @@ const isShort = nameLength < minLength; |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/id-match" | ||
url: "https://eslint.org/docs/latest/rules/id-match" | ||
}, | ||
@@ -71,2 +71,3 @@ | ||
const sourceCode = context.sourceCode; | ||
let globalScope; | ||
@@ -175,4 +176,4 @@ | ||
Program() { | ||
globalScope = context.getScope(); | ||
Program(node) { | ||
globalScope = sourceCode.getScope(node); | ||
}, | ||
@@ -179,0 +180,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/implicit-arrow-linebreak" | ||
url: "https://eslint.org/docs/latest/rules/implicit-arrow-linebreak" | ||
}, | ||
@@ -38,3 +38,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const option = context.options[0] || "beside"; | ||
@@ -41,0 +41,0 @@ |
@@ -31,3 +31,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/indent-legacy" | ||
url: "https://eslint.org/docs/latest/rules/indent-legacy" | ||
}, | ||
@@ -210,3 +210,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -213,0 +213,0 @@ if (context.options.length) { |
@@ -178,2 +178,3 @@ /** | ||
"no-obj-calls": () => require("./no-obj-calls"), | ||
"no-object-constructor": () => require("./no-object-constructor"), | ||
"no-octal": () => require("./no-octal"), | ||
@@ -180,0 +181,0 @@ "no-octal-escape": () => require("./no-octal-escape"), |
@@ -53,3 +53,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/init-declarations" | ||
url: "https://eslint.org/docs/latest/rules/init-declarations" | ||
}, | ||
@@ -56,0 +56,0 @@ |
@@ -47,3 +47,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/jsx-quotes" | ||
url: "https://eslint.org/docs/latest/rules/jsx-quotes" | ||
}, | ||
@@ -50,0 +50,0 @@ |
@@ -12,10 +12,4 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const GraphemeSplitter = require("grapheme-splitter"); | ||
const { getGraphemeCount } = require("../shared/string-utils"); | ||
const splitter = new GraphemeSplitter(); | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
/** | ||
@@ -148,3 +142,3 @@ * Checks whether a string contains a line terminator as defined in | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/key-spacing" | ||
url: "https://eslint.org/docs/latest/rules/key-spacing" | ||
}, | ||
@@ -337,3 +331,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -354,2 +348,36 @@ /** | ||
/** | ||
* Starting from the given node (a property.key node here) looks forward | ||
* until it finds the colon punctuator and returns it. | ||
* @param {ASTNode} node The node to start looking from. | ||
* @returns {ASTNode} The colon punctuator. | ||
*/ | ||
function getNextColon(node) { | ||
return sourceCode.getTokenAfter(node, astUtils.isColonToken); | ||
} | ||
/** | ||
* Starting from the given node (a property.key node here) looks forward | ||
* until it finds the last token before a colon punctuator and returns it. | ||
* @param {ASTNode} node The node to start looking from. | ||
* @returns {ASTNode} The last token before a colon punctuator. | ||
*/ | ||
function getLastTokenBeforeColon(node) { | ||
const colonToken = getNextColon(node); | ||
return sourceCode.getTokenBefore(colonToken); | ||
} | ||
/** | ||
* Starting from the given node (a property.key node here) looks forward | ||
* until it finds the first token after a colon punctuator and returns it. | ||
* @param {ASTNode} node The node to start looking from. | ||
* @returns {ASTNode} The first token after a colon punctuator. | ||
*/ | ||
function getFirstTokenAfterColon(node) { | ||
const colonToken = getNextColon(node); | ||
return sourceCode.getTokenAfter(colonToken); | ||
} | ||
/** | ||
* Checks whether a property is a member of the property group it follows. | ||
@@ -362,3 +390,3 @@ * @param {ASTNode} lastMember The last Property known to be in the group. | ||
const groupEndLine = lastMember.loc.start.line, | ||
candidateValueStartLine = (isKeyValueProperty(candidate) ? candidate.value : candidate).loc.start.line; | ||
candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line; | ||
@@ -393,24 +421,2 @@ if (candidateValueStartLine - groupEndLine <= 1) { | ||
/** | ||
* Starting from the given a node (a property.key node here) looks forward | ||
* until it finds the last token before a colon punctuator and returns it. | ||
* @param {ASTNode} node The node to start looking from. | ||
* @returns {ASTNode} The last token before a colon punctuator. | ||
*/ | ||
function getLastTokenBeforeColon(node) { | ||
const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken); | ||
return sourceCode.getTokenBefore(colonToken); | ||
} | ||
/** | ||
* Starting from the given a node (a property.key node here) looks forward | ||
* until it finds the colon punctuator and returns it. | ||
* @param {ASTNode} node The node to start looking from. | ||
* @returns {ASTNode} The colon punctuator. | ||
*/ | ||
function getNextColon(node) { | ||
return sourceCode.getTokenAfter(node, astUtils.isColonToken); | ||
} | ||
/** | ||
* Gets an object literal property's key as the identifier name or string value. | ||
@@ -520,3 +526,3 @@ * @param {ASTNode} property Property node whose key to retrieve. | ||
return splitter.countGraphemes(sourceCode.getText().slice(startToken.range[0], endToken.range[1])); | ||
return getGraphemeCount(sourceCode.getText().slice(startToken.range[0], endToken.range[1])); | ||
} | ||
@@ -523,0 +529,0 @@ |
@@ -72,3 +72,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/keyword-spacing" | ||
url: "https://eslint.org/docs/latest/rules/keyword-spacing" | ||
}, | ||
@@ -112,3 +112,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -115,0 +115,0 @@ const tokensToIgnore = new WeakSet(); |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/line-comment-position" | ||
url: "https://eslint.org/docs/latest/rules/line-comment-position" | ||
}, | ||
@@ -82,3 +82,3 @@ | ||
const customIgnoreRegExp = new RegExp(ignorePattern, "u"); | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -85,0 +85,0 @@ //-------------------------------------------------------------------------- |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/linebreak-style" | ||
url: "https://eslint.org/docs/latest/rules/linebreak-style" | ||
}, | ||
@@ -44,3 +44,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -47,0 +47,0 @@ //-------------------------------------------------------------------------- |
@@ -60,3 +60,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/lines-around-comment" | ||
url: "https://eslint.org/docs/latest/rules/lines-around-comment" | ||
}, | ||
@@ -117,2 +117,6 @@ | ||
type: "boolean" | ||
}, | ||
afterHashbangComment: { | ||
type: "boolean", | ||
default: false | ||
} | ||
@@ -139,3 +143,3 @@ }, | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -455,2 +459,9 @@ const lines = sourceCode.lines, | ||
} | ||
} else if (token.type === "Shebang") { | ||
if (options.afterHashbangComment) { | ||
checkForEmptyLine(token, { | ||
after: options.afterHashbangComment, | ||
before: false | ||
}); | ||
} | ||
} | ||
@@ -457,0 +468,0 @@ }); |
@@ -23,3 +23,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/lines-around-directive" | ||
url: "https://eslint.org/docs/latest/rules/lines-around-directive" | ||
}, | ||
@@ -58,3 +58,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const config = context.options[0] || "always"; | ||
@@ -61,0 +61,0 @@ const expectLineBefore = typeof config === "string" ? config : config.before; |
@@ -14,2 +14,17 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
/** | ||
* Types of class members. | ||
* Those have `test` method to check it matches to the given class member. | ||
* @private | ||
*/ | ||
const ClassMemberTypes = { | ||
"*": { test: () => true }, | ||
field: { test: node => node.type === "PropertyDefinition" }, | ||
method: { test: node => node.type === "MethodDefinition" } | ||
}; | ||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -26,3 +41,3 @@ //------------------------------------------------------------------------------ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/lines-between-class-members" | ||
url: "https://eslint.org/docs/latest/rules/lines-between-class-members" | ||
}, | ||
@@ -34,3 +49,28 @@ | ||
{ | ||
enum: ["always", "never"] | ||
anyOf: [ | ||
{ | ||
type: "object", | ||
properties: { | ||
enforce: { | ||
type: "array", | ||
items: { | ||
type: "object", | ||
properties: { | ||
blankLine: { enum: ["always", "never"] }, | ||
prev: { enum: ["method", "field", "*"] }, | ||
next: { enum: ["method", "field", "*"] } | ||
}, | ||
additionalProperties: false, | ||
required: ["blankLine", "prev", "next"] | ||
}, | ||
minItems: 1 | ||
} | ||
}, | ||
additionalProperties: false, | ||
required: ["enforce"] | ||
}, | ||
{ | ||
enum: ["always", "never"] | ||
} | ||
] | ||
}, | ||
@@ -61,3 +101,4 @@ { | ||
const sourceCode = context.getSourceCode(); | ||
const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }]; | ||
const sourceCode = context.sourceCode; | ||
@@ -151,2 +192,34 @@ /** | ||
/** | ||
* Checks whether the given node matches the given type. | ||
* @param {ASTNode} node The class member node to check. | ||
* @param {string} type The class member type to check. | ||
* @returns {boolean} `true` if the class member node matched the type. | ||
* @private | ||
*/ | ||
function match(node, type) { | ||
return ClassMemberTypes[type].test(node); | ||
} | ||
/** | ||
* Finds the last matched configuration from the configureList. | ||
* @param {ASTNode} prevNode The previous node to match. | ||
* @param {ASTNode} nextNode The current node to match. | ||
* @returns {string|null} Padding type or `null` if no matches were found. | ||
* @private | ||
*/ | ||
function getPaddingType(prevNode, nextNode) { | ||
for (let i = configureList.length - 1; i >= 0; --i) { | ||
const configure = configureList[i]; | ||
const matched = | ||
match(prevNode, configure.prev) && | ||
match(nextNode, configure.next); | ||
if (matched) { | ||
return configure.blankLine; | ||
} | ||
} | ||
return null; | ||
} | ||
return { | ||
@@ -166,8 +239,9 @@ ClassBody(node) { | ||
const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0); | ||
const paddingType = getPaddingType(body[i], body[i + 1]); | ||
if ((options[0] === "always" && !skip && !isPadded) || | ||
(options[0] === "never" && isPadded)) { | ||
if (paddingType === "never" && isPadded) { | ||
context.report({ | ||
node: body[i + 1], | ||
messageId: isPadded ? "never" : "always", | ||
messageId: "never", | ||
fix(fixer) { | ||
@@ -177,8 +251,19 @@ if (hasTokenInPadding) { | ||
} | ||
return isPadded | ||
? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n") | ||
: fixer.insertTextAfter(curLineLastToken, "\n"); | ||
return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n"); | ||
} | ||
}); | ||
} else if (paddingType === "always" && !skip && !isPadded) { | ||
context.report({ | ||
node: body[i + 1], | ||
messageId: "always", | ||
fix(fixer) { | ||
if (hasTokenInPadding) { | ||
return null; | ||
} | ||
return fixer.insertTextAfter(curLineLastToken, "\n"); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -185,0 +270,0 @@ } |
@@ -115,3 +115,3 @@ /** | ||
* truthiness checks: value, Boolean(value), !!value | ||
* falsyness checks: !value, !Boolean(value) | ||
* falsiness checks: !value, !Boolean(value) | ||
* nullish checks: value == null, value === undefined || value === null | ||
@@ -154,2 +154,27 @@ * @param {ASTNode} expression Test condition | ||
/** | ||
* Gets the leftmost operand of a consecutive logical expression. | ||
* @param {SourceCode} sourceCode The ESLint source code object | ||
* @param {LogicalExpression} node LogicalExpression | ||
* @returns {Expression} Leftmost operand | ||
*/ | ||
function getLeftmostOperand(sourceCode, node) { | ||
let left = node.left; | ||
while (left.type === "LogicalExpression" && left.operator === node.operator) { | ||
if (astUtils.isParenthesised(sourceCode, left)) { | ||
/* | ||
* It should have associativity, | ||
* but ignore it if use parentheses to make the evaluation order clear. | ||
*/ | ||
return left; | ||
} | ||
left = left.left; | ||
} | ||
return left; | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -164,5 +189,5 @@ // Rule Definition | ||
docs: { | ||
description: "Require or disallow logical assignment logical operator shorthand", | ||
description: "Require or disallow logical assignment operator shorthand", | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/logical-assignment-operators" | ||
url: "https://eslint.org/docs/latest/rules/logical-assignment-operators" | ||
}, | ||
@@ -194,3 +219,2 @@ | ||
fixable: "code", | ||
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- Does not detect conditional suggestions | ||
hasSuggestions: true, | ||
@@ -212,4 +236,4 @@ messages: { | ||
const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements; | ||
const sourceCode = context.getSourceCode(); | ||
const isStrict = context.getScope().isStrict; | ||
const sourceCode = context.sourceCode; | ||
const isStrict = sourceCode.getScope(sourceCode.ast).isStrict; | ||
@@ -327,3 +351,6 @@ /** | ||
"AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) { | ||
if (!astUtils.isSameReference(assignment.left, assignment.right.left)) { | ||
const leftOperand = getLeftmostOperand(sourceCode, assignment.right); | ||
if (!astUtils.isSameReference(assignment.left, leftOperand) | ||
) { | ||
return; | ||
@@ -352,6 +379,6 @@ } | ||
// -> foo ||= bar | ||
const logicalOperatorToken = getOperatorToken(assignment.right); | ||
const logicalOperatorToken = getOperatorToken(leftOperand.parent); | ||
const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken); | ||
yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]); | ||
yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]); | ||
} | ||
@@ -381,4 +408,7 @@ }; | ||
const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && | ||
(astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent)); | ||
const parentPrecedence = astUtils.getPrecedence(logical.parent); | ||
const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && ( | ||
parentPrecedence === -1 || | ||
astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence | ||
); | ||
@@ -420,3 +450,3 @@ if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) { | ||
const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent; | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(ifNode); | ||
const existence = getExistence(ifNode.test, scope); | ||
@@ -423,0 +453,0 @@ |
@@ -24,3 +24,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-classes-per-file" | ||
url: "https://eslint.org/docs/latest/rules/max-classes-per-file" | ||
}, | ||
@@ -27,0 +27,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-depth" | ||
url: "https://eslint.org/docs/latest/rules/max-depth" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -74,3 +74,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-len" | ||
url: "https://eslint.org/docs/latest/rules/max-len" | ||
}, | ||
@@ -101,3 +101,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -257,15 +257,19 @@ /** | ||
/** | ||
* A reducer to group an AST node by line number, both start and end. | ||
* @param {Object} acc the accumulator | ||
* @param {ASTNode} node the AST node in question | ||
* @returns {Object} the modified accumulator | ||
* @private | ||
* | ||
* reduce an array of AST nodes by line number, both start and end. | ||
* @param {ASTNode[]} arr array of AST nodes | ||
* @returns {Object} accululated AST nodes | ||
*/ | ||
function groupByLineNumber(acc, node) { | ||
for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) { | ||
ensureArrayAndPush(acc, i, node); | ||
function groupArrayByLineNumber(arr) { | ||
const obj = {}; | ||
for (let i = 0; i < arr.length; i++) { | ||
const node = arr[i]; | ||
for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) { | ||
ensureArrayAndPush(obj, j, node); | ||
} | ||
} | ||
return acc; | ||
return obj; | ||
} | ||
@@ -318,9 +322,9 @@ | ||
const strings = getAllStrings(); | ||
const stringsByLine = strings.reduce(groupByLineNumber, {}); | ||
const stringsByLine = groupArrayByLineNumber(strings); | ||
const templateLiterals = getAllTemplateLiterals(); | ||
const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {}); | ||
const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals); | ||
const regExpLiterals = getAllRegExpLiterals(); | ||
const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {}); | ||
const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals); | ||
@@ -327,0 +331,0 @@ lines.forEach((line, i) => { |
@@ -76,3 +76,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-lines-per-function" | ||
url: "https://eslint.org/docs/latest/rules/max-lines-per-function" | ||
}, | ||
@@ -89,3 +89,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const lines = sourceCode.lines; | ||
@@ -92,0 +92,0 @@ |
@@ -39,3 +39,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-lines" | ||
url: "https://eslint.org/docs/latest/rules/max-lines" | ||
}, | ||
@@ -91,3 +91,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -94,0 +94,0 @@ /** |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-nested-callbacks" | ||
url: "https://eslint.org/docs/latest/rules/max-nested-callbacks" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-params" | ||
url: "https://eslint.org/docs/latest/rules/max-params" | ||
}, | ||
@@ -61,3 +61,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const option = context.options[0]; | ||
@@ -64,0 +64,0 @@ let numParams = 3; |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-statements-per-line" | ||
url: "https://eslint.org/docs/latest/rules/max-statements-per-line" | ||
}, | ||
@@ -49,3 +49,3 @@ | ||
const sourceCode = context.getSourceCode(), | ||
const sourceCode = context.sourceCode, | ||
options = context.options[0] || {}, | ||
@@ -52,0 +52,0 @@ maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/max-statements" | ||
url: "https://eslint.org/docs/latest/rules/max-statements" | ||
}, | ||
@@ -30,0 +30,0 @@ |
@@ -21,7 +21,37 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/multiline-comment-style" | ||
url: "https://eslint.org/docs/latest/rules/multiline-comment-style" | ||
}, | ||
fixable: "whitespace", | ||
schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }], | ||
schema: { | ||
anyOf: [ | ||
{ | ||
type: "array", | ||
items: [ | ||
{ | ||
enum: ["starred-block", "bare-block"] | ||
} | ||
], | ||
additionalItems: false | ||
}, | ||
{ | ||
type: "array", | ||
items: [ | ||
{ | ||
enum: ["separate-lines"] | ||
}, | ||
{ | ||
type: "object", | ||
properties: { | ||
checkJSDoc: { | ||
type: "boolean" | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
], | ||
additionalItems: false | ||
} | ||
] | ||
}, | ||
messages: { | ||
@@ -39,4 +69,6 @@ expectedBlock: "Expected a block comment instead of consecutive line comments.", | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const option = context.options[0] || "starred-block"; | ||
const params = context.options[1] || {}; | ||
const checkJSDoc = !!params.checkJSDoc; | ||
@@ -338,7 +370,14 @@ //---------------------------------------------------------------------- | ||
if (firstComment.type !== "Block" || isJSDocComment(commentGroup)) { | ||
const isJSDoc = isJSDocComment(commentGroup); | ||
if (firstComment.type !== "Block" || (!checkJSDoc && isJSDoc)) { | ||
return; | ||
} | ||
const commentLines = getCommentLines(commentGroup); | ||
let commentLines = getCommentLines(commentGroup); | ||
if (isJSDoc) { | ||
commentLines = commentLines.slice(1, commentLines.length - 1); | ||
} | ||
const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true }); | ||
@@ -345,0 +384,0 @@ |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/multiline-ternary" | ||
url: "https://eslint.org/docs/latest/rules/multiline-ternary" | ||
}, | ||
@@ -43,3 +43,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const option = context.options[0]; | ||
@@ -78,3 +78,3 @@ const multiline = option !== "never"; | ||
messageId: "unexpectedTestCons", | ||
fix: fixer => { | ||
fix(fixer) { | ||
if (hasComments) { | ||
@@ -107,3 +107,3 @@ return null; | ||
messageId: "unexpectedConsAlt", | ||
fix: fixer => { | ||
fix(fixer) { | ||
if (hasComments) { | ||
@@ -110,0 +110,0 @@ return null; |
@@ -87,3 +87,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/new-cap" | ||
url: "https://eslint.org/docs/latest/rules/new-cap" | ||
}, | ||
@@ -151,3 +151,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -154,0 +154,0 @@ //-------------------------------------------------------------------------- |
@@ -30,20 +30,11 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/new-parens" | ||
url: "https://eslint.org/docs/latest/rules/new-parens" | ||
}, | ||
fixable: "code", | ||
schema: { | ||
anyOf: [ | ||
{ | ||
type: "array", | ||
items: [ | ||
{ | ||
enum: ["always", "never"] | ||
} | ||
], | ||
minItems: 0, | ||
maxItems: 1 | ||
} | ||
] | ||
}, | ||
schema: [ | ||
{ | ||
enum: ["always", "never"] | ||
} | ||
], | ||
messages: { | ||
@@ -59,3 +50,3 @@ missing: "Missing '()' invoking a constructor.", | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -62,0 +53,0 @@ return { |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/newline-after-var" | ||
url: "https://eslint.org/docs/latest/rules/newline-after-var" | ||
}, | ||
@@ -47,3 +47,3 @@ schema: [ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -217,3 +217,2 @@ // Default `mode` to "always". | ||
messageId: "unexpected", | ||
data: { identifier: node.name }, | ||
fix(fixer) { | ||
@@ -237,3 +236,2 @@ const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER); | ||
messageId: "expected", | ||
data: { identifier: node.name }, | ||
fix(fixer) { | ||
@@ -240,0 +238,0 @@ if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) { |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/newline-before-return" | ||
url: "https://eslint.org/docs/latest/rules/newline-before-return" | ||
}, | ||
@@ -35,3 +35,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -38,0 +38,0 @@ //-------------------------------------------------------------------------- |
@@ -23,3 +23,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/newline-per-chained-call" | ||
url: "https://eslint.org/docs/latest/rules/newline-per-chained-call" | ||
}, | ||
@@ -51,3 +51,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -54,0 +54,0 @@ /** |
@@ -93,3 +93,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-alert" | ||
url: "https://eslint.org/docs/latest/rules/no-alert" | ||
}, | ||
@@ -105,6 +105,8 @@ | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
CallExpression(node) { | ||
const callee = skipChainExpression(node.callee), | ||
currentScope = context.getScope(); | ||
currentScope = sourceCode.getScope(node); | ||
@@ -111,0 +113,0 @@ // without window. |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-array-constructor" | ||
url: "https://eslint.org/docs/latest/rules/no-array-constructor" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -19,3 +19,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-async-promise-executor" | ||
url: "https://eslint.org/docs/latest/rules/no-async-promise-executor" | ||
}, | ||
@@ -34,3 +34,3 @@ | ||
context.report({ | ||
node: context.getSourceCode().getFirstToken(node.arguments[0], token => token.value === "async"), | ||
node: context.sourceCode.getFirstToken(node.arguments[0], token => token.value === "async"), | ||
messageId: "async" | ||
@@ -37,0 +37,0 @@ }); |
@@ -64,3 +64,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-await-in-loop" | ||
url: "https://eslint.org/docs/latest/rules/no-await-in-loop" | ||
}, | ||
@@ -67,0 +67,0 @@ |
@@ -31,3 +31,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-bitwise" | ||
url: "https://eslint.org/docs/latest/rules/no-bitwise" | ||
}, | ||
@@ -34,0 +34,0 @@ |
@@ -24,3 +24,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-buffer-constructor" | ||
url: "https://eslint.org/docs/latest/rules/no-buffer-constructor" | ||
}, | ||
@@ -27,0 +27,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-caller" | ||
url: "https://eslint.org/docs/latest/rules/no-caller" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -19,3 +19,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-case-declarations" | ||
url: "https://eslint.org/docs/latest/rules/no-case-declarations" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-catch-shadow" | ||
url: "https://eslint.org/docs/latest/rules/no-catch-shadow" | ||
}, | ||
@@ -43,2 +43,4 @@ | ||
const sourceCode = context.sourceCode; | ||
//-------------------------------------------------------------------------- | ||
@@ -65,3 +67,3 @@ // Helpers | ||
"CatchClause[param!=null]"(node) { | ||
let scope = context.getScope(); | ||
let scope = sourceCode.getScope(node); | ||
@@ -68,0 +70,0 @@ /* |
@@ -22,3 +22,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-class-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-class-assign" | ||
}, | ||
@@ -35,2 +35,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -54,3 +56,3 @@ * Finds and reports references that are non initializer and writable. | ||
function checkForClass(node) { | ||
context.getDeclaredVariables(node).forEach(checkVariable); | ||
sourceCode.getDeclaredVariables(node).forEach(checkVariable); | ||
} | ||
@@ -57,0 +59,0 @@ |
@@ -19,3 +19,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-compare-neg-zero" | ||
url: "https://eslint.org/docs/latest/rules/no-compare-neg-zero" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -39,3 +39,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-cond-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-cond-assign" | ||
}, | ||
@@ -61,3 +61,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -64,0 +64,0 @@ /** |
@@ -36,3 +36,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-confusing-arrow" | ||
url: "https://eslint.org/docs/latest/rules/no-confusing-arrow" | ||
}, | ||
@@ -60,3 +60,3 @@ | ||
const onlyOneSimpleParam = config.onlyOneSimpleParam; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -63,0 +63,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-console" | ||
url: "https://eslint.org/docs/latest/rules/no-console" | ||
}, | ||
@@ -55,2 +55,3 @@ | ||
const allowed = options.allow || []; | ||
const sourceCode = context.sourceCode; | ||
@@ -114,4 +115,4 @@ /** | ||
return { | ||
"Program:exit"() { | ||
const scope = context.getScope(); | ||
"Program:exit"(node) { | ||
const scope = sourceCode.getScope(node); | ||
const consoleVar = astUtils.getVariableByName(scope, "console"); | ||
@@ -118,0 +119,0 @@ const shadowed = consoleVar && consoleVar.defs.length > 0; |
@@ -22,3 +22,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-const-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-const-assign" | ||
}, | ||
@@ -35,2 +35,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -50,3 +52,3 @@ * Finds and reports references that are non initializer and writable. | ||
if (node.kind === "const") { | ||
context.getDeclaredVariables(node).forEach(checkVariable); | ||
sourceCode.getDeclaredVariables(node).forEach(checkVariable); | ||
} | ||
@@ -53,0 +55,0 @@ } |
@@ -18,2 +18,19 @@ /** | ||
/** | ||
* Checks whether or not a node is `null` or `undefined`. Similar to the one | ||
* found in ast-utils.js, but this one correctly handles the edge case that | ||
* `undefined` has been redefined. | ||
* @param {Scope} scope Scope in which the expression was found. | ||
* @param {ASTNode} node A node to check. | ||
* @returns {boolean} Whether or not the node is a `null` or `undefined`. | ||
* @public | ||
*/ | ||
function isNullOrUndefined(scope, node) { | ||
return ( | ||
isNullLiteral(node) || | ||
(node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) || | ||
(node.type === "UnaryExpression" && node.operator === "void") | ||
); | ||
} | ||
/** | ||
* Test if an AST node has a statically knowable constant nullishness. Meaning, | ||
@@ -25,5 +42,10 @@ * it will always resolve to a constant value of either: `null`, `undefined` | ||
* @param {ASTNode} node The AST node being tested. | ||
* @param {boolean} nonNullish if `true` then nullish values are not considered constant. | ||
* @returns {boolean} Does `node` have constant nullishness? | ||
*/ | ||
function hasConstantNullishness(scope, node) { | ||
function hasConstantNullishness(scope, node, nonNullish) { | ||
if (nonNullish && isNullOrUndefined(scope, node)) { | ||
return false; | ||
} | ||
switch (node.type) { | ||
@@ -50,5 +72,8 @@ case "ObjectExpression": // Objects are never nullish | ||
} | ||
case "LogicalExpression": { | ||
return node.operator === "??" && hasConstantNullishness(scope, node.right, true); | ||
} | ||
case "AssignmentExpression": | ||
if (node.operator === "=") { | ||
return hasConstantNullishness(scope, node.right); | ||
return hasConstantNullishness(scope, node.right, nonNullish); | ||
} | ||
@@ -86,3 +111,3 @@ | ||
return hasConstantNullishness(scope, last); | ||
return hasConstantNullishness(scope, last, nonNullish); | ||
} | ||
@@ -355,3 +380,3 @@ case "Identifier": | ||
* | ||
* Catching these is especially useful for primitive constructures | ||
* Catching these is especially useful for primitive constructors | ||
* which return boxed values, a surprising gotcha' in JavaScript. | ||
@@ -387,20 +412,2 @@ */ | ||
/** | ||
* Checks whether or not a node is `null` or `undefined`. Similar to the one | ||
* found in ast-utils.js, but this one correctly handles the edge case that | ||
* `undefined` has been redefined. | ||
* @param {Scope} scope Scope in which the expression was found. | ||
* @param {ASTNode} node A node to check. | ||
* @returns {boolean} Whether or not the node is a `null` or `undefined`. | ||
* @public | ||
*/ | ||
function isNullOrUndefined(scope, node) { | ||
return ( | ||
isNullLiteral(node) || | ||
(node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) || | ||
(node.type === "UnaryExpression" && node.operator === "void") | ||
); | ||
} | ||
/** | ||
* Checks if one operand will cause the result to be constant. | ||
@@ -416,3 +423,3 @@ * @param {Scope} scope Scope in which the expression was found. | ||
if ( | ||
(isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) || | ||
(isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || | ||
(isStaticBoolean(scope, a) && hasConstantLooseBooleanComparison(scope, b)) | ||
@@ -424,3 +431,3 @@ ) { | ||
if ( | ||
(isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) || | ||
(isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || | ||
(isStaticBoolean(scope, a) && hasConstantStrictBooleanComparison(scope, b)) | ||
@@ -445,3 +452,3 @@ ) { | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-constant-binary-expression" | ||
url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression" | ||
}, | ||
@@ -458,10 +465,12 @@ schema: [], | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
LogicalExpression(node) { | ||
const { operator, left } = node; | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) { | ||
context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } }); | ||
} else if (operator === "??" && hasConstantNullishness(scope, left)) { | ||
} else if (operator === "??" && hasConstantNullishness(scope, left, false)) { | ||
context.report({ node: left, messageId: "constantShortCircuit", data: { property: "nullishness", operator } }); | ||
@@ -471,3 +480,3 @@ } | ||
BinaryExpression(node) { | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
const { right, left, operator } = node; | ||
@@ -474,0 +483,0 @@ const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator); |
@@ -26,3 +26,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-constant-condition" | ||
url: "https://eslint.org/docs/latest/rules/no-constant-condition" | ||
}, | ||
@@ -52,2 +52,3 @@ | ||
loopSetStack = []; | ||
const sourceCode = context.sourceCode; | ||
@@ -67,3 +68,3 @@ let loopsInCurrentScope = new Set(); | ||
function trackConstantConditionLoop(node) { | ||
if (node.test && isConstant(context.getScope(), node.test, true)) { | ||
if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { | ||
loopsInCurrentScope.add(node); | ||
@@ -93,3 +94,3 @@ } | ||
function reportIfConstant(node) { | ||
if (node.test && isConstant(context.getScope(), node.test, true)) { | ||
if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { | ||
context.report({ node: node.test, messageId: "unexpected" }); | ||
@@ -96,0 +97,0 @@ } |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-constructor-return" | ||
url: "https://eslint.org/docs/latest/rules/no-constructor-return" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-continue" | ||
url: "https://eslint.org/docs/latest/rules/no-continue" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -8,3 +8,3 @@ /** | ||
const RegExpValidator = require("regexpp").RegExpValidator; | ||
const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; | ||
const collector = new (class { | ||
@@ -18,2 +18,12 @@ constructor() { | ||
onPatternEnter() { | ||
/* | ||
* `RegExpValidator` may parse the pattern twice in one `validatePattern`. | ||
* So `this._controlChars` should be cleared here as well. | ||
* | ||
* For example, the `/(?<a>\x1f)/` regex will parse the pattern twice. | ||
* This is based on the content described in Annex B. | ||
* If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice. | ||
* See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb | ||
*/ | ||
this._controlChars = []; | ||
@@ -37,6 +47,9 @@ } | ||
const uFlag = typeof flags === "string" && flags.includes("u"); | ||
const vFlag = typeof flags === "string" && flags.includes("v"); | ||
this._controlChars = []; | ||
this._source = regexpStr; | ||
try { | ||
this._source = regexpStr; | ||
this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook | ||
this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook | ||
} catch { | ||
@@ -62,3 +75,3 @@ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-control-regex" | ||
url: "https://eslint.org/docs/latest/rules/no-control-regex" | ||
}, | ||
@@ -65,0 +78,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-debugger" | ||
url: "https://eslint.org/docs/latest/rules/no-debugger" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-delete-var" | ||
url: "https://eslint.org/docs/latest/rules/no-delete-var" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -18,5 +18,5 @@ /** | ||
docs: { | ||
description: "Disallow division operators explicitly at the beginning of regular expressions", | ||
description: "Disallow equal signs explicitly at the beginning of regular expressions", | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-div-regex" | ||
url: "https://eslint.org/docs/latest/rules/no-div-regex" | ||
}, | ||
@@ -34,3 +34,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -37,0 +37,0 @@ return { |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-dupe-args" | ||
url: "https://eslint.org/docs/latest/rules/no-dupe-args" | ||
}, | ||
@@ -33,2 +33,4 @@ | ||
const sourceCode = context.sourceCode; | ||
//-------------------------------------------------------------------------- | ||
@@ -54,3 +56,3 @@ // Helpers | ||
function checkParams(node) { | ||
const variables = context.getDeclaredVariables(node); | ||
const variables = sourceCode.getDeclaredVariables(node); | ||
@@ -57,0 +59,0 @@ for (let i = 0; i < variables.length; ++i) { |
@@ -22,3 +22,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-dupe-class-members" | ||
url: "https://eslint.org/docs/latest/rules/no-dupe-class-members" | ||
}, | ||
@@ -25,0 +25,0 @@ |
@@ -57,3 +57,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-dupe-else-if" | ||
url: "https://eslint.org/docs/latest/rules/no-dupe-else-if" | ||
}, | ||
@@ -69,3 +69,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -72,0 +72,0 @@ /** |
@@ -93,3 +93,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-dupe-keys" | ||
url: "https://eslint.org/docs/latest/rules/no-dupe-keys" | ||
}, | ||
@@ -96,0 +96,0 @@ |
@@ -27,3 +27,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-duplicate-case" | ||
url: "https://eslint.org/docs/latest/rules/no-duplicate-case" | ||
}, | ||
@@ -39,3 +39,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -42,0 +42,0 @@ /** |
@@ -238,3 +238,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-duplicate-imports" | ||
url: "https://eslint.org/docs/latest/rules/no-duplicate-imports" | ||
}, | ||
@@ -241,0 +241,0 @@ |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-else-return" | ||
url: "https://eslint.org/docs/latest/rules/no-else-return" | ||
}, | ||
@@ -51,2 +51,4 @@ | ||
const sourceCode = context.sourceCode; | ||
//-------------------------------------------------------------------------- | ||
@@ -174,21 +176,20 @@ // Helpers | ||
* Display the context report if rule is violated | ||
* @param {Node} node The 'else' node | ||
* @param {Node} elseNode The 'else' node | ||
* @returns {void} | ||
*/ | ||
function displayReport(node) { | ||
const currentScope = context.getScope(); | ||
function displayReport(elseNode) { | ||
const currentScope = sourceCode.getScope(elseNode.parent); | ||
context.report({ | ||
node, | ||
node: elseNode, | ||
messageId: "unexpected", | ||
fix: fixer => { | ||
fix(fixer) { | ||
if (!isSafeFromNameCollisions(node, currentScope)) { | ||
if (!isSafeFromNameCollisions(elseNode, currentScope)) { | ||
return null; | ||
} | ||
const sourceCode = context.getSourceCode(); | ||
const startToken = sourceCode.getFirstToken(node); | ||
const startToken = sourceCode.getFirstToken(elseNode); | ||
const elseToken = sourceCode.getTokenBefore(startToken); | ||
const source = sourceCode.getText(node); | ||
const source = sourceCode.getText(elseNode); | ||
const lastIfToken = sourceCode.getTokenBefore(elseToken); | ||
@@ -209,3 +210,3 @@ let fixedSource, firstTokenOfElseBlock; | ||
*/ | ||
const ifBlockMaybeUnsafe = node.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; | ||
const ifBlockMaybeUnsafe = elseNode.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; | ||
const elseBlockUnsafe = /^[([/+`-]/u.test(firstTokenOfElseBlock.value); | ||
@@ -217,3 +218,3 @@ | ||
const endToken = sourceCode.getLastToken(node); | ||
const endToken = sourceCode.getLastToken(elseNode); | ||
const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken); | ||
@@ -252,4 +253,4 @@ | ||
return new FixTracker(fixer, sourceCode) | ||
.retainEnclosingFunction(node) | ||
.replaceTextRange([elseToken.range[0], node.range[1]], fixedSource); | ||
.retainEnclosingFunction(elseNode) | ||
.replaceTextRange([elseToken.range[0], elseNode.range[1]], fixedSource); | ||
} | ||
@@ -256,0 +257,0 @@ }); |
@@ -9,15 +9,13 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
/* | ||
* plain-English description of the following regexp: | ||
* 0. `^` fix the match at the beginning of the string | ||
* 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following | ||
* 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) | ||
* 1.1. `\\.`: an escape sequence | ||
* 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty | ||
* 2. `$`: fix the match at the end of the string | ||
*/ | ||
const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u; | ||
const parser = new RegExpParser(); | ||
const QUICK_TEST_REGEX = /\[\]/u; | ||
@@ -36,3 +34,3 @@ //------------------------------------------------------------------------------ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-empty-character-class" | ||
url: "https://eslint.org/docs/latest/rules/no-empty-character-class" | ||
}, | ||
@@ -50,5 +48,28 @@ | ||
"Literal[regex]"(node) { | ||
if (!regex.test(node.regex.pattern)) { | ||
context.report({ node, messageId: "unexpected" }); | ||
const { pattern, flags } = node.regex; | ||
if (!QUICK_TEST_REGEX.test(pattern)) { | ||
return; | ||
} | ||
let regExpAST; | ||
try { | ||
regExpAST = parser.parsePattern(pattern, 0, pattern.length, { | ||
unicode: flags.includes("u"), | ||
unicodeSets: flags.includes("v") | ||
}); | ||
} catch { | ||
// Ignore regular expressions that regexpp cannot parse | ||
return; | ||
} | ||
visitRegExpAST(regExpAST, { | ||
onCharacterClassEnter(characterClass) { | ||
if (!characterClass.negate && characterClass.elements.length === 0) { | ||
context.report({ node, messageId: "unexpected" }); | ||
} | ||
} | ||
}); | ||
} | ||
@@ -55,0 +76,0 @@ }; |
@@ -100,3 +100,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-empty-function" | ||
url: "https://eslint.org/docs/latest/rules/no-empty-function" | ||
}, | ||
@@ -127,3 +127,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -130,0 +130,0 @@ /** |
@@ -7,2 +7,4 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
@@ -20,6 +22,17 @@ // Rule Definition | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-empty-pattern" | ||
url: "https://eslint.org/docs/latest/rules/no-empty-pattern" | ||
}, | ||
schema: [], | ||
schema: [ | ||
{ | ||
type: "object", | ||
properties: { | ||
allowObjectPatternsAsParameters: { | ||
type: "boolean", | ||
default: false | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
], | ||
@@ -32,7 +45,29 @@ messages: { | ||
create(context) { | ||
const options = context.options[0] || {}, | ||
allowObjectPatternsAsParameters = options.allowObjectPatternsAsParameters || false; | ||
return { | ||
ObjectPattern(node) { | ||
if (node.properties.length === 0) { | ||
context.report({ node, messageId: "unexpected", data: { type: "object" } }); | ||
if (node.properties.length > 0) { | ||
return; | ||
} | ||
// Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true | ||
if ( | ||
allowObjectPatternsAsParameters && | ||
( | ||
astUtils.isFunction(node.parent) || | ||
( | ||
node.parent.type === "AssignmentPattern" && | ||
astUtils.isFunction(node.parent.parent) && | ||
node.parent.right.type === "ObjectExpression" && | ||
node.parent.right.properties.length === 0 | ||
) | ||
) | ||
) { | ||
return; | ||
} | ||
context.report({ node, messageId: "unexpected", data: { type: "object" } }); | ||
}, | ||
@@ -39,0 +74,0 @@ ArrayPattern(node) { |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-empty-static-block" | ||
url: "https://eslint.org/docs/latest/rules/no-empty-static-block" | ||
}, | ||
@@ -31,3 +31,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -34,0 +34,0 @@ return { |
@@ -26,3 +26,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-empty" | ||
url: "https://eslint.org/docs/latest/rules/no-empty" | ||
}, | ||
@@ -53,3 +53,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -56,0 +56,0 @@ return { |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-eq-null" | ||
url: "https://eslint.org/docs/latest/rules/no-eq-null" | ||
}, | ||
@@ -24,0 +24,0 @@ |
@@ -48,3 +48,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-eval" | ||
url: "https://eslint.org/docs/latest/rules/no-eval" | ||
}, | ||
@@ -72,7 +72,7 @@ | ||
); | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let funcInfo = null; | ||
/** | ||
* Pushs a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. | ||
* Pushes a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. | ||
* Top-level scopes are handled separately. | ||
@@ -89,3 +89,3 @@ * | ||
function enterThisScope(node) { | ||
const strict = context.getScope().isStrict; | ||
const strict = sourceCode.getScope(node).isStrict; | ||
@@ -227,3 +227,3 @@ funcInfo = { | ||
Program(node) { | ||
const scope = context.getScope(), | ||
const scope = sourceCode.getScope(node), | ||
features = context.parserOptions.ecmaFeatures || {}, | ||
@@ -246,4 +246,4 @@ strict = | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -250,0 +250,0 @@ exitThisScope(); |
@@ -22,3 +22,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-ex-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-ex-assign" | ||
}, | ||
@@ -35,2 +35,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -49,3 +51,3 @@ * Finds and reports references that are non initializer and writable. | ||
CatchClause(node) { | ||
context.getDeclaredVariables(node).forEach(checkVariable); | ||
sourceCode.getDeclaredVariables(node).forEach(checkVariable); | ||
} | ||
@@ -52,0 +54,0 @@ }; |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-extend-native" | ||
url: "https://eslint.org/docs/latest/rules/no-extend-native" | ||
}, | ||
@@ -55,2 +55,3 @@ | ||
const config = context.options[0] || {}; | ||
const sourceCode = context.sourceCode; | ||
const exceptions = new Set(config.exceptions || []); | ||
@@ -164,4 +165,4 @@ const modifiedBuiltins = new Set( | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -168,0 +169,0 @@ modifiedBuiltins.forEach(builtin => { |
@@ -31,3 +31,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-extra-bind" | ||
url: "https://eslint.org/docs/latest/rules/no-extra-bind" | ||
}, | ||
@@ -44,3 +44,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let scopeInfo = null; | ||
@@ -47,0 +47,0 @@ |
@@ -13,3 +13,3 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const eslintUtils = require("eslint-utils"); | ||
const eslintUtils = require("@eslint-community/eslint-utils"); | ||
@@ -30,3 +30,3 @@ const precedence = astUtils.getPrecedence; | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-extra-boolean-cast" | ||
url: "https://eslint.org/docs/latest/rules/no-extra-boolean-cast" | ||
}, | ||
@@ -53,3 +53,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -56,0 +56,0 @@ // Node types which have a test which will coerce values to booleans. |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-extra-label" | ||
url: "https://eslint.org/docs/latest/rules/no-extra-label" | ||
}, | ||
@@ -39,3 +39,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let scopeInfo = null; | ||
@@ -42,0 +42,0 @@ |
@@ -11,3 +11,3 @@ /** | ||
const { isParenthesized: isParenthesizedRaw } = require("eslint-utils"); | ||
const { isParenthesized: isParenthesizedRaw } = require("@eslint-community/eslint-utils"); | ||
const astUtils = require("./utils/ast-utils.js"); | ||
@@ -23,3 +23,3 @@ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-extra-parens" | ||
url: "https://eslint.org/docs/latest/rules/no-extra-parens" | ||
}, | ||
@@ -51,2 +51,3 @@ | ||
conditionalAssign: { type: "boolean" }, | ||
ternaryOperandBinaryExpressions: { type: "boolean" }, | ||
nestedBinaryExpressions: { type: "boolean" }, | ||
@@ -76,3 +77,3 @@ returnAssign: { type: "boolean" }, | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -83,2 +84,3 @@ const tokensToIgnore = new WeakSet(); | ||
const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; | ||
const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false; | ||
const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false; | ||
@@ -395,2 +397,26 @@ const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false; | ||
/** | ||
* Checks if a node is fixable. | ||
* A node is fixable if removing a single pair of surrounding parentheses does not turn it | ||
* into a directive after fixing other nodes. | ||
* Almost all nodes are fixable, except if all of the following conditions are met: | ||
* The node is a string Literal | ||
* It has a single pair of parentheses | ||
* It is the only child of an ExpressionStatement | ||
* @param {ASTNode} node The node to evaluate. | ||
* @returns {boolean} Whether or not the node is fixable. | ||
* @private | ||
*/ | ||
function isFixable(node) { | ||
// if it's not a string literal it can be autofixed | ||
if (node.type !== "Literal" || typeof node.value !== "string") { | ||
return true; | ||
} | ||
if (isParenthesisedTwice(node)) { | ||
return true; | ||
} | ||
return !astUtils.isTopLevelExpressionStatement(node.parent); | ||
} | ||
/** | ||
* Report the node | ||
@@ -438,10 +464,12 @@ * @param {ASTNode} node node to evaluate | ||
messageId: "unexpected", | ||
fix(fixer) { | ||
const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]); | ||
fix: isFixable(node) | ||
? fixer => { | ||
const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]); | ||
return fixer.replaceTextRange([ | ||
leftParenToken.range[0], | ||
rightParenToken.range[1] | ||
], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : "")); | ||
} | ||
return fixer.replaceTextRange([ | ||
leftParenToken.range[0], | ||
rightParenToken.range[1] | ||
], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : "")); | ||
} | ||
: null | ||
}); | ||
@@ -776,2 +804,34 @@ } | ||
/** | ||
* Checks if the left-hand side of an assignment is an identifier, the operator is one of | ||
* `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous class or function. | ||
* | ||
* As per https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation, an | ||
* assignment involving one of the operators `=`, `&&=`, `||=` or `??=` where the right-hand | ||
* side is an anonymous class or function and the left-hand side is an *unparenthesized* | ||
* identifier has different semantics than other assignments. | ||
* Specifically, when an expression like `foo = function () {}` is evaluated, `foo.name` | ||
* will be set to the string "foo", i.e. the identifier name. The same thing does not happen | ||
* when evaluating `(foo) = function () {}`. | ||
* Since the parenthesizing of the identifier in the left-hand side is significant in this | ||
* special case, the parentheses, if present, should not be flagged as unnecessary. | ||
* @param {ASTNode} node an AssignmentExpression node. | ||
* @returns {boolean} `true` if the left-hand side of the assignment is an identifier, the | ||
* operator is one of `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous | ||
* class or function; otherwise, `false`. | ||
*/ | ||
function isAnonymousFunctionAssignmentException({ left, operator, right }) { | ||
if (left.type === "Identifier" && ["=", "&&=", "||=", "??="].includes(operator)) { | ||
const rhsType = right.type; | ||
if (rhsType === "ArrowFunctionExpression") { | ||
return true; | ||
} | ||
if ((rhsType === "FunctionExpression" || rhsType === "ClassExpression") && !right.id) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
return { | ||
@@ -815,3 +875,4 @@ ArrayExpression(node) { | ||
AssignmentExpression(node) { | ||
if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left)) { | ||
if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left) && | ||
(!isAnonymousFunctionAssignmentException(node) || isParenthesisedTwice(node.left))) { | ||
report(node.left); | ||
@@ -839,3 +900,7 @@ } | ||
} | ||
const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]); | ||
if ( | ||
!(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) && | ||
!isCondAssignException(node) && | ||
@@ -847,7 +912,11 @@ hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" })) | ||
if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { | ||
if ( | ||
!(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) && | ||
hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { | ||
report(node.consequent); | ||
} | ||
if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { | ||
if ( | ||
!(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) && | ||
hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { | ||
report(node.alternate); | ||
@@ -854,0 +923,0 @@ } |
@@ -27,3 +27,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-extra-semi" | ||
url: "https://eslint.org/docs/latest/rules/no-extra-semi" | ||
}, | ||
@@ -40,5 +40,22 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
/** | ||
* Checks if a node or token is fixable. | ||
* A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes. | ||
* @param {Token} nodeOrToken The node or token to check. | ||
* @returns {boolean} Whether or not the node is fixable. | ||
*/ | ||
function isFixable(nodeOrToken) { | ||
const nextToken = sourceCode.getTokenAfter(nodeOrToken); | ||
if (!nextToken || nextToken.type !== "String") { | ||
return true; | ||
} | ||
const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]); | ||
return !astUtils.isTopLevelExpressionStatement(stringNode.parent); | ||
} | ||
/** | ||
* Reports an unnecessary semicolon error. | ||
@@ -52,13 +69,14 @@ * @param {Node|Token} nodeOrToken A node or a token to be reported. | ||
messageId: "unexpected", | ||
fix(fixer) { | ||
fix: isFixable(nodeOrToken) | ||
? fixer => | ||
/* | ||
* Expand the replacement range to include the surrounding | ||
* tokens to avoid conflicting with semi. | ||
* https://github.com/eslint/eslint/issues/7928 | ||
*/ | ||
return new FixTracker(fixer, context.getSourceCode()) | ||
.retainSurroundingTokens(nodeOrToken) | ||
.remove(nodeOrToken); | ||
} | ||
/* | ||
* Expand the replacement range to include the surrounding | ||
* tokens to avoid conflicting with semi. | ||
* https://github.com/eslint/eslint/issues/7928 | ||
*/ | ||
new FixTracker(fixer, context.sourceCode) | ||
.retainSurroundingTokens(nodeOrToken) | ||
.remove(nodeOrToken) | ||
: null | ||
}); | ||
@@ -65,0 +83,0 @@ } |
@@ -8,2 +8,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const { directivesPattern } = require("../shared/directives"); | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
@@ -15,2 +21,28 @@ //------------------------------------------------------------------------------ | ||
/** | ||
* Checks all segments in a set and returns true if any are reachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if any segment is reachable; false otherwise. | ||
*/ | ||
function isAnySegmentReachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive. | ||
* @param {string} comment The comment string to check. | ||
* @param {RegExp} fallthroughCommentPattern The regular expression used for checking for fallthrough comments. | ||
* @returns {boolean} `true` if the comment string is truly a fallthrough comment. | ||
*/ | ||
function isFallThroughComment(comment, fallthroughCommentPattern) { | ||
return fallthroughCommentPattern.test(comment) && !directivesPattern.test(comment.trim()); | ||
} | ||
/** | ||
* Checks whether or not a given case has a fallthrough comment. | ||
@@ -24,3 +56,3 @@ * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through. | ||
function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -31,3 +63,3 @@ if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") { | ||
if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) { | ||
if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) { | ||
return true; | ||
@@ -39,15 +71,6 @@ } | ||
return Boolean(comment && fallthroughCommentPattern.test(comment.value)); | ||
return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern)); | ||
} | ||
/** | ||
* Checks whether or not a given code path segment is reachable. | ||
* @param {CodePathSegment} segment A CodePathSegment to check. | ||
* @returns {boolean} `true` if the segment is reachable. | ||
*/ | ||
function isReachable(segment) { | ||
return segment.reachable; | ||
} | ||
/** | ||
* Checks whether a node and a token are separated by blank lines | ||
@@ -74,3 +97,3 @@ * @param {ASTNode} node The node to check | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-fallthrough" | ||
url: "https://eslint.org/docs/latest/rules/no-fallthrough" | ||
}, | ||
@@ -102,4 +125,5 @@ | ||
const options = context.options[0] || {}; | ||
let currentCodePath = null; | ||
const sourceCode = context.getSourceCode(); | ||
const codePathSegments = []; | ||
let currentCodePathSegments = new Set(); | ||
const sourceCode = context.sourceCode; | ||
const allowEmptyCase = options.allowEmptyCase || false; | ||
@@ -120,9 +144,29 @@ | ||
return { | ||
onCodePathStart(codePath) { | ||
currentCodePath = codePath; | ||
onCodePathStart() { | ||
codePathSegments.push(currentCodePathSegments); | ||
currentCodePathSegments = new Set(); | ||
}, | ||
onCodePathEnd() { | ||
currentCodePath = currentCodePath.upper; | ||
currentCodePathSegments = codePathSegments.pop(); | ||
}, | ||
onUnreachableCodePathSegmentStart(segment) { | ||
currentCodePathSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
currentCodePathSegments.delete(segment); | ||
}, | ||
onCodePathSegmentStart(segment) { | ||
currentCodePathSegments.add(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
currentCodePathSegments.delete(segment); | ||
}, | ||
SwitchCase(node) { | ||
@@ -152,3 +196,3 @@ | ||
*/ | ||
if (currentCodePath.currentSegments.some(isReachable) && | ||
if (isAnySegmentReachable(currentCodePathSegments) && | ||
(node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) && | ||
@@ -155,0 +199,0 @@ node.parent.cases[node.parent.cases.length - 1] !== node) { |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-floating-decimal" | ||
url: "https://eslint.org/docs/latest/rules/no-floating-decimal" | ||
}, | ||
@@ -39,3 +39,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -42,0 +42,0 @@ return { |
@@ -22,3 +22,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-func-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-func-assign" | ||
}, | ||
@@ -35,2 +35,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -70,3 +72,3 @@ * Reports a reference if is non initializer and writable. | ||
function checkForFunction(node) { | ||
context.getDeclaredVariables(node).forEach(checkVariable); | ||
sourceCode.getDeclaredVariables(node).forEach(checkVariable); | ||
} | ||
@@ -73,0 +75,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-global-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-global-assign" | ||
}, | ||
@@ -45,2 +45,3 @@ | ||
const config = context.options[0]; | ||
const sourceCode = context.sourceCode; | ||
const exceptions = (config && config.exceptions) || []; | ||
@@ -89,4 +90,4 @@ | ||
return { | ||
Program() { | ||
const globalScope = context.getScope(); | ||
Program(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -93,0 +94,0 @@ globalScope.variables.forEach(checkVariable); |
@@ -196,3 +196,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-implicit-coercion" | ||
url: "https://eslint.org/docs/latest/rules/no-implicit-coercion" | ||
}, | ||
@@ -239,3 +239,3 @@ | ||
const options = parseOptions(context.options[0] || {}); | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -242,0 +242,0 @@ /** |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-implicit-globals" | ||
url: "https://eslint.org/docs/latest/rules/no-implicit-globals" | ||
}, | ||
@@ -47,2 +47,3 @@ | ||
const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true; | ||
const sourceCode = context.sourceCode; | ||
@@ -67,4 +68,4 @@ /** | ||
return { | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
@@ -71,0 +72,0 @@ scope.variables.forEach(variable => { |
@@ -13,3 +13,3 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const { getStaticValue } = require("eslint-utils"); | ||
const { getStaticValue } = require("@eslint-community/eslint-utils"); | ||
@@ -28,3 +28,3 @@ //------------------------------------------------------------------------------ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-implied-eval" | ||
url: "https://eslint.org/docs/latest/rules/no-implied-eval" | ||
}, | ||
@@ -42,2 +42,3 @@ | ||
const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; | ||
const sourceCode = context.sourceCode; | ||
@@ -72,3 +73,3 @@ /** | ||
const staticValue = getStaticValue(firstArgument, context.getScope()); | ||
const staticValue = getStaticValue(firstArgument, sourceCode.getScope(node)); | ||
const isStaticString = staticValue && typeof staticValue.value === "string"; | ||
@@ -124,4 +125,4 @@ const isString = isStaticString || isEvaluatedString(firstArgument); | ||
}, | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -128,0 +129,0 @@ GLOBAL_CANDIDATES |
@@ -12,3 +12,3 @@ /** | ||
const { findVariable } = require("eslint-utils"); | ||
const { findVariable } = require("@eslint-community/eslint-utils"); | ||
const astUtils = require("./utils/ast-utils"); | ||
@@ -186,3 +186,3 @@ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-import-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-import-assign" | ||
}, | ||
@@ -199,7 +199,9 @@ | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
ImportDeclaration(node) { | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
for (const variable of context.getDeclaredVariables(node)) { | ||
for (const variable of sourceCode.getDeclaredVariables(node)) { | ||
const shouldCheckMembers = variable.defs.some( | ||
@@ -206,0 +208,0 @@ d => d.node.type === "ImportNamespaceSpecifier" |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-inline-comments" | ||
url: "https://eslint.org/docs/latest/rules/no-inline-comments" | ||
}, | ||
@@ -43,3 +43,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const options = context.options[0]; | ||
@@ -46,0 +46,0 @@ let customIgnoreRegExp; |
@@ -53,3 +53,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-inner-declarations" | ||
url: "https://eslint.org/docs/latest/rules/no-inner-declarations" | ||
}, | ||
@@ -56,0 +56,0 @@ |
@@ -11,5 +11,5 @@ /** | ||
const RegExpValidator = require("regexpp").RegExpValidator; | ||
const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; | ||
const validator = new RegExpValidator(); | ||
const validFlags = /[dgimsuy]/gu; | ||
const validFlags = /[dgimsuvy]/gu; | ||
const undefined1 = void 0; | ||
@@ -29,3 +29,3 @@ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-invalid-regexp" | ||
url: "https://eslint.org/docs/latest/rules/no-invalid-regexp" | ||
}, | ||
@@ -113,8 +113,10 @@ | ||
* @param {string} pattern The RegExp pattern to validate. | ||
* @param {boolean} uFlag The Unicode flag. | ||
* @param {Object} flags The RegExp flags to validate. | ||
* @param {boolean} [flags.unicode] The Unicode flag. | ||
* @param {boolean} [flags.unicodeSets] The UnicodeSets flag. | ||
* @returns {string|null} The syntax error. | ||
*/ | ||
function validateRegExpPattern(pattern, uFlag) { | ||
function validateRegExpPattern(pattern, flags) { | ||
try { | ||
validator.validatePattern(pattern, undefined1, undefined1, uFlag); | ||
validator.validatePattern(pattern, undefined1, undefined1, flags); | ||
return null; | ||
@@ -137,6 +139,15 @@ } catch (err) { | ||
validator.validateFlags(flags); | ||
return null; | ||
} catch { | ||
return `Invalid flags supplied to RegExp constructor '${flags}'`; | ||
} | ||
/* | ||
* `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`, | ||
* but this rule may check only the flag when the pattern is unidentifiable, so check it here. | ||
* https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern | ||
*/ | ||
if (flags.includes("u") && flags.includes("v")) { | ||
return "Regex 'u' and 'v' flags cannot be used together"; | ||
} | ||
return null; | ||
} | ||
@@ -173,4 +184,8 @@ | ||
flags === null | ||
? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) | ||
: validateRegExpPattern(pattern, flags.includes("u")) | ||
? ( | ||
validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) && | ||
validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) && | ||
validateRegExpPattern(pattern, { unicode: false, unicodeSets: false }) | ||
) | ||
: validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }) | ||
); | ||
@@ -177,0 +192,0 @@ |
@@ -41,3 +41,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-invalid-this" | ||
url: "https://eslint.org/docs/latest/rules/no-invalid-this" | ||
}, | ||
@@ -67,3 +67,3 @@ | ||
const stack = [], | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -100,3 +100,3 @@ /** | ||
if (codePath.origin === "program") { | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
const features = context.parserOptions.ecmaFeatures || {}; | ||
@@ -126,3 +126,3 @@ | ||
stack.push({ | ||
init: !context.getScope().isStrict, | ||
init: !sourceCode.getScope(node).isStrict, | ||
node, | ||
@@ -129,0 +129,0 @@ valid: true |
@@ -36,3 +36,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-irregular-whitespace" | ||
url: "https://eslint.org/docs/latest/rules/no-irregular-whitespace" | ||
}, | ||
@@ -59,2 +59,6 @@ | ||
default: false | ||
}, | ||
skipJSXText: { | ||
type: "boolean", | ||
default: false | ||
} | ||
@@ -82,4 +86,5 @@ }, | ||
const skipTemplates = !!options.skipTemplates; | ||
const skipJSXText = !!options.skipJSXText; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const commentNodes = sourceCode.getAllComments(); | ||
@@ -106,3 +111,3 @@ | ||
/** | ||
* Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors | ||
* Checks literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors | ||
* @param {ASTNode} node to check for matching errors. | ||
@@ -112,3 +117,3 @@ * @returns {void} | ||
*/ | ||
function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { | ||
function removeInvalidNodeErrorsInLiteral(node) { | ||
const shouldCheckStrings = skipStrings && (typeof node.value === "string"); | ||
@@ -153,2 +158,14 @@ const shouldCheckRegExps = skipRegExps && Boolean(node.regex); | ||
/** | ||
* Checks JSX nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors | ||
* @param {ASTNode} node to check for matching errors. | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function removeInvalidNodeErrorsInJSXText(node) { | ||
if (ALL_IRREGULARS.test(node.raw)) { | ||
removeWhitespaceError(node); | ||
} | ||
} | ||
/** | ||
* Checks the program source for irregular whitespace | ||
@@ -246,5 +263,5 @@ * @param {ASTNode} node The program node | ||
nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral; | ||
nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral; | ||
nodes.Literal = removeInvalidNodeErrorsInLiteral; | ||
nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop; | ||
nodes.JSXText = skipJSXText ? removeInvalidNodeErrorsInJSXText : noop; | ||
nodes["Program:exit"] = function() { | ||
@@ -251,0 +268,0 @@ if (skipComments) { |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-iterator" | ||
url: "https://eslint.org/docs/latest/rules/no-iterator" | ||
}, | ||
@@ -29,0 +29,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-label-var" | ||
url: "https://eslint.org/docs/latest/rules/no-label-var" | ||
}, | ||
@@ -38,2 +38,3 @@ | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
@@ -64,3 +65,3 @@ //-------------------------------------------------------------------------- | ||
// Fetch the innermost scope. | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
@@ -67,0 +68,0 @@ /* |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-labels" | ||
url: "https://eslint.org/docs/latest/rules/no-labels" | ||
}, | ||
@@ -28,0 +28,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-lone-blocks" | ||
url: "https://eslint.org/docs/latest/rules/no-lone-blocks" | ||
}, | ||
@@ -37,2 +37,3 @@ | ||
let ruleDef; | ||
const sourceCode = context.sourceCode; | ||
@@ -72,5 +73,6 @@ /** | ||
* and "marks it" as valid if any. | ||
* @param {ASTNode} node The current node to check. | ||
* @returns {void} | ||
*/ | ||
function markLoneBlock() { | ||
function markLoneBlock(node) { | ||
if (loneBlocks.length === 0) { | ||
@@ -80,3 +82,3 @@ return; | ||
const block = context.getAncestors().pop(); | ||
const block = node.parent; | ||
@@ -123,9 +125,9 @@ if (loneBlocks[loneBlocks.length - 1] === block) { | ||
if (node.kind === "let" || node.kind === "const") { | ||
markLoneBlock(); | ||
markLoneBlock(node); | ||
} | ||
}; | ||
ruleDef.FunctionDeclaration = function() { | ||
if (context.getScope().isStrict) { | ||
markLoneBlock(); | ||
ruleDef.FunctionDeclaration = function(node) { | ||
if (sourceCode.getScope(node).isStrict) { | ||
markLoneBlock(node); | ||
} | ||
@@ -132,0 +134,0 @@ }; |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-lonely-if" | ||
url: "https://eslint.org/docs/latest/rules/no-lonely-if" | ||
}, | ||
@@ -32,9 +32,8 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
return { | ||
IfStatement(node) { | ||
const ancestors = context.getAncestors(), | ||
parent = ancestors.pop(), | ||
grandparent = ancestors.pop(); | ||
const parent = node.parent, | ||
grandparent = parent.parent; | ||
@@ -41,0 +40,0 @@ if (parent && parent.type === "BlockStatement" && |
@@ -159,3 +159,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-loop-func" | ||
url: "https://eslint.org/docs/latest/rules/no-loop-func" | ||
}, | ||
@@ -172,2 +172,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -188,4 +190,4 @@ * Reports functions which match the following condition: | ||
const references = context.getScope().through; | ||
const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name); | ||
const references = sourceCode.getScope(node).through; | ||
const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name); | ||
@@ -192,0 +194,0 @@ if (unsafeRefs.length > 0) { |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-loss-of-precision" | ||
url: "https://eslint.org/docs/latest/rules/no-loss-of-precision" | ||
}, | ||
@@ -87,3 +87,3 @@ schema: [], | ||
function addDecimalPointToNumber(stringNumber) { | ||
return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`; | ||
return `${stringNumber[0]}.${stringNumber.slice(1)}`; | ||
} | ||
@@ -97,3 +97,8 @@ | ||
function removeLeadingZeros(numberAsString) { | ||
return numberAsString.replace(/^0*/u, ""); | ||
for (let i = 0; i < numberAsString.length; i++) { | ||
if (numberAsString[i] !== "0") { | ||
return numberAsString.slice(i); | ||
} | ||
} | ||
return numberAsString; | ||
} | ||
@@ -107,3 +112,8 @@ | ||
function removeTrailingZeros(numberAsString) { | ||
return numberAsString.replace(/0*$/u, ""); | ||
for (let i = numberAsString.length - 1; i >= 0; i--) { | ||
if (numberAsString[i] !== "0") { | ||
return numberAsString.slice(0, i + 1); | ||
} | ||
} | ||
return numberAsString; | ||
} | ||
@@ -135,3 +145,3 @@ | ||
if (trimmedFloat.startsWith(".")) { | ||
const decimalDigits = trimmedFloat.split(".").pop(); | ||
const decimalDigits = trimmedFloat.slice(1); | ||
const significantDigits = removeLeadingZeros(decimalDigits); | ||
@@ -152,3 +162,2 @@ | ||
/** | ||
@@ -169,3 +178,2 @@ * Converts a base ten number to proper scientific notation | ||
return `${normalizedCoefficient}e${magnitude}`; | ||
} | ||
@@ -172,0 +180,0 @@ |
@@ -37,3 +37,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-magic-numbers" | ||
url: "https://eslint.org/docs/latest/rules/no-magic-numbers" | ||
}, | ||
@@ -40,0 +40,0 @@ |
@@ -6,6 +6,7 @@ /** | ||
const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); | ||
const { RegExpValidator, RegExpParser, visitRegExpAST } = require("regexpp"); | ||
const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); | ||
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); | ||
const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); | ||
const astUtils = require("./utils/ast-utils.js"); | ||
const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); | ||
@@ -16,3 +17,6 @@ //------------------------------------------------------------------------------ | ||
const REGEXPP_LATEST_ECMA_VERSION = 2022; | ||
/** | ||
* @typedef {import('@eslint-community/regexpp').AST.Character} Character | ||
* @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement | ||
*/ | ||
@@ -24,6 +28,8 @@ /** | ||
* so this function reverts CharacterClassRange syntax and restore the sequence. | ||
* @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences. | ||
* @returns {IterableIterator<number[]>} The list of character sequences. | ||
* @param {CharacterClassElement[]} nodes The node list to iterate character sequences. | ||
* @returns {IterableIterator<Character[]>} The list of character sequences. | ||
*/ | ||
function *iterateCharacterSequence(nodes) { | ||
/** @type {Character[]} */ | ||
let seq = []; | ||
@@ -34,12 +40,15 @@ | ||
case "Character": | ||
seq.push(node.value); | ||
seq.push(node); | ||
break; | ||
case "CharacterClassRange": | ||
seq.push(node.min.value); | ||
seq.push(node.min); | ||
yield seq; | ||
seq = [node.max.value]; | ||
seq = [node.max]; | ||
break; | ||
case "CharacterSet": | ||
case "CharacterClass": // [[]] nesting character class | ||
case "ClassStringDisjunction": // \q{...} | ||
case "ExpressionCharacterClass": // [A--B] | ||
if (seq.length > 0) { | ||
@@ -60,12 +69,54 @@ yield seq; | ||
/** | ||
* Checks whether the given character node is a Unicode code point escape or not. | ||
* @param {Character} char the character node to check. | ||
* @returns {boolean} `true` if the character node is a Unicode code point escape. | ||
*/ | ||
function isUnicodeCodePointEscape(char) { | ||
return /^\\u\{[\da-f]+\}$/iu.test(char.raw); | ||
} | ||
/** | ||
* Each function returns `true` if it detects that kind of problem. | ||
* @type {Record<string, (chars: Character[]) => boolean>} | ||
*/ | ||
const hasCharacterSequence = { | ||
surrogatePairWithoutUFlag(chars) { | ||
return chars.some((c, i) => i !== 0 && isSurrogatePair(chars[i - 1], c)); | ||
return chars.some((c, i) => { | ||
if (i === 0) { | ||
return false; | ||
} | ||
const c1 = chars[i - 1]; | ||
return ( | ||
isSurrogatePair(c1.value, c.value) && | ||
!isUnicodeCodePointEscape(c1) && | ||
!isUnicodeCodePointEscape(c) | ||
); | ||
}); | ||
}, | ||
surrogatePair(chars) { | ||
return chars.some((c, i) => { | ||
if (i === 0) { | ||
return false; | ||
} | ||
const c1 = chars[i - 1]; | ||
return ( | ||
isSurrogatePair(c1.value, c.value) && | ||
( | ||
isUnicodeCodePointEscape(c1) || | ||
isUnicodeCodePointEscape(c) | ||
) | ||
); | ||
}); | ||
}, | ||
combiningClass(chars) { | ||
return chars.some((c, i) => ( | ||
i !== 0 && | ||
isCombiningCharacter(c) && | ||
!isCombiningCharacter(chars[i - 1]) | ||
isCombiningCharacter(c.value) && | ||
!isCombiningCharacter(chars[i - 1].value) | ||
)); | ||
@@ -77,4 +128,4 @@ }, | ||
i !== 0 && | ||
isEmojiModifier(c) && | ||
!isEmojiModifier(chars[i - 1]) | ||
isEmojiModifier(c.value) && | ||
!isEmojiModifier(chars[i - 1].value) | ||
)); | ||
@@ -86,4 +137,4 @@ }, | ||
i !== 0 && | ||
isRegionalIndicatorSymbol(c) && | ||
isRegionalIndicatorSymbol(chars[i - 1]) | ||
isRegionalIndicatorSymbol(c.value) && | ||
isRegionalIndicatorSymbol(chars[i - 1].value) | ||
)); | ||
@@ -98,5 +149,5 @@ }, | ||
i !== lastIndex && | ||
c === 0x200d && | ||
chars[i - 1] !== 0x200d && | ||
chars[i + 1] !== 0x200d | ||
c.value === 0x200d && | ||
chars[i - 1].value !== 0x200d && | ||
chars[i + 1].value !== 0x200d | ||
)); | ||
@@ -120,3 +171,3 @@ } | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-misleading-character-class" | ||
url: "https://eslint.org/docs/latest/rules/no-misleading-character-class" | ||
}, | ||
@@ -130,2 +181,3 @@ | ||
surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.", | ||
surrogatePair: "Unexpected surrogate pair in character class.", | ||
combiningClass: "Unexpected combined character in character class.", | ||
@@ -139,3 +191,3 @@ emojiModifier: "Unexpected modified Emoji in character class.", | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const parser = new RegExpParser(); | ||
@@ -159,3 +211,6 @@ | ||
pattern.length, | ||
flags.includes("u") | ||
{ | ||
unicode: flags.includes("u"), | ||
unicodeSets: flags.includes("v") | ||
} | ||
); | ||
@@ -200,34 +255,6 @@ } catch { | ||
/** | ||
* Checks if the given regular expression pattern would be valid with the `u` flag. | ||
* @param {string} pattern The regular expression pattern to verify. | ||
* @returns {boolean} `true` if the pattern would be valid with the `u` flag. | ||
* `false` if the pattern would be invalid with the `u` flag or the configured | ||
* ecmaVersion doesn't support the `u` flag. | ||
*/ | ||
function isValidWithUnicodeFlag(pattern) { | ||
const { ecmaVersion } = context.languageOptions; | ||
// ecmaVersion <= 5 doesn't support the 'u' flag | ||
if (ecmaVersion <= 5) { | ||
return false; | ||
} | ||
const validator = new RegExpValidator({ | ||
ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) | ||
}); | ||
try { | ||
validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); | ||
} catch { | ||
return false; | ||
} | ||
return true; | ||
} | ||
return { | ||
"Literal[regex]"(node) { | ||
verify(node, node.regex.pattern, node.regex.flags, fixer => { | ||
if (!isValidWithUnicodeFlag(node.regex.pattern)) { | ||
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) { | ||
return null; | ||
@@ -239,4 +266,4 @@ } | ||
}, | ||
"Program"() { | ||
const scope = context.getScope(); | ||
"Program"(node) { | ||
const scope = sourceCode.getScope(node); | ||
const tracker = new ReferenceTracker(scope); | ||
@@ -249,6 +276,6 @@ | ||
*/ | ||
for (const { node } of tracker.iterateGlobalReferences({ | ||
for (const { node: refNode } of tracker.iterateGlobalReferences({ | ||
RegExp: { [CALL]: true, [CONSTRUCT]: true } | ||
})) { | ||
const [patternNode, flagsNode] = node.arguments; | ||
const [patternNode, flagsNode] = refNode.arguments; | ||
const pattern = getStringIfConstant(patternNode, scope); | ||
@@ -258,10 +285,10 @@ const flags = getStringIfConstant(flagsNode, scope); | ||
if (typeof pattern === "string") { | ||
verify(node, pattern, flags || "", fixer => { | ||
verify(refNode, pattern, flags || "", fixer => { | ||
if (!isValidWithUnicodeFlag(pattern)) { | ||
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { | ||
return null; | ||
} | ||
if (node.arguments.length === 1) { | ||
const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis | ||
if (refNode.arguments.length === 1) { | ||
const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis | ||
@@ -268,0 +295,0 @@ return fixer.insertTextAfter( |
@@ -93,3 +93,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-mixed-operators" | ||
url: "https://eslint.org/docs/latest/rules/no-mixed-operators" | ||
}, | ||
@@ -126,3 +126,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const options = normalizeOptions(context.options[0]); | ||
@@ -129,0 +129,0 @@ |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-mixed-requires" | ||
url: "https://eslint.org/docs/latest/rules/no-mixed-requires" | ||
}, | ||
@@ -28,0 +28,0 @@ |
@@ -19,3 +19,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs" | ||
url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs" | ||
}, | ||
@@ -35,3 +35,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -38,0 +38,0 @@ let smartTabs; |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-multi-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-multi-assign" | ||
}, | ||
@@ -24,0 +24,0 @@ |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-multi-spaces" | ||
url: "https://eslint.org/docs/latest/rules/no-multi-spaces" | ||
}, | ||
@@ -56,3 +56,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const options = context.options[0] || {}; | ||
@@ -59,0 +59,0 @@ const ignoreEOLComments = options.ignoreEOLComments; |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-multi-str" | ||
url: "https://eslint.org/docs/latest/rules/no-multi-str" | ||
}, | ||
@@ -29,0 +29,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-multiple-empty-lines" | ||
url: "https://eslint.org/docs/latest/rules/no-multiple-empty-lines" | ||
}, | ||
@@ -68,3 +68,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -71,0 +71,0 @@ // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-native-reassign" | ||
url: "https://eslint.org/docs/latest/rules/no-native-reassign" | ||
}, | ||
@@ -51,2 +51,3 @@ | ||
const exceptions = (config && config.exceptions) || []; | ||
const sourceCode = context.sourceCode; | ||
@@ -92,4 +93,4 @@ /** | ||
return { | ||
Program() { | ||
const globalScope = context.getScope(); | ||
Program(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -96,0 +97,0 @@ globalScope.variables.forEach(checkVariable); |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-negated-condition" | ||
url: "https://eslint.org/docs/latest/rules/no-negated-condition" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-negated-in-lhs" | ||
url: "https://eslint.org/docs/latest/rules/no-negated-in-lhs" | ||
}, | ||
@@ -24,0 +24,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-nested-ternary" | ||
url: "https://eslint.org/docs/latest/rules/no-nested-ternary" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -32,3 +32,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-new-func" | ||
url: "https://eslint.org/docs/latest/rules/no-new-func" | ||
}, | ||
@@ -44,6 +44,7 @@ | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
const variable = globalScope.set.get("Function"); | ||
@@ -53,8 +54,8 @@ | ||
variable.references.forEach(ref => { | ||
const node = ref.identifier; | ||
const { parent } = node; | ||
const idNode = ref.identifier; | ||
const { parent } = idNode; | ||
let evalNode; | ||
if (parent) { | ||
if (node === parent.callee && ( | ||
if (idNode === parent.callee && ( | ||
parent.type === "NewExpression" || | ||
@@ -66,3 +67,3 @@ parent.type === "CallExpression" | ||
parent.type === "MemberExpression" && | ||
node === parent.object && | ||
idNode === parent.object && | ||
callMethods.has(astUtils.getStaticPropertyName(parent)) | ||
@@ -69,0 +70,0 @@ ) { |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-new-native-nonconstructor" | ||
url: "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" | ||
}, | ||
@@ -39,5 +39,7 @@ | ||
const sourceCode = context.sourceCode; | ||
return { | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -49,8 +51,8 @@ for (const nonConstructorName of nonConstructorGlobalFunctionNames) { | ||
variable.references.forEach(ref => { | ||
const node = ref.identifier; | ||
const parent = node.parent; | ||
const idNode = ref.identifier; | ||
const parent = idNode.parent; | ||
if (parent && parent.type === "NewExpression" && parent.callee === node) { | ||
if (parent && parent.type === "NewExpression" && parent.callee === idNode) { | ||
context.report({ | ||
node, | ||
node: idNode, | ||
messageId: "noNewNonconstructor", | ||
@@ -57,0 +59,0 @@ data: { name: nonConstructorName } |
/** | ||
* @fileoverview A rule to disallow calls to the Object constructor | ||
* @author Matt DuVall <http://www.mattduvall.com/> | ||
* @deprecated in ESLint v8.50.0 | ||
*/ | ||
@@ -26,5 +27,11 @@ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-new-object" | ||
url: "https://eslint.org/docs/latest/rules/no-new-object" | ||
}, | ||
deprecated: true, | ||
replacedBy: [ | ||
"no-object-constructor" | ||
], | ||
schema: [], | ||
@@ -38,6 +45,9 @@ | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
NewExpression(node) { | ||
const variable = astUtils.getVariableByName( | ||
context.getScope(), | ||
sourceCode.getScope(node), | ||
node.callee.name | ||
@@ -44,0 +54,0 @@ ); |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-new-require" | ||
url: "https://eslint.org/docs/latest/rules/no-new-require" | ||
}, | ||
@@ -28,0 +28,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-new-symbol" | ||
url: "https://eslint.org/docs/latest/rules/no-new-symbol" | ||
}, | ||
@@ -33,5 +33,7 @@ | ||
const sourceCode = context.sourceCode; | ||
return { | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
const variable = globalScope.set.get("Symbol"); | ||
@@ -41,8 +43,8 @@ | ||
variable.references.forEach(ref => { | ||
const node = ref.identifier; | ||
const parent = node.parent; | ||
const idNode = ref.identifier; | ||
const parent = idNode.parent; | ||
if (parent && parent.type === "NewExpression" && parent.callee === node) { | ||
if (parent && parent.type === "NewExpression" && parent.callee === idNode) { | ||
context.report({ | ||
node, | ||
node: idNode, | ||
messageId: "noNewSymbol" | ||
@@ -49,0 +51,0 @@ }); |
@@ -9,2 +9,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const { getVariableByName } = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -21,3 +27,3 @@ //------------------------------------------------------------------------------ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-new-wrappers" | ||
url: "https://eslint.org/docs/latest/rules/no-new-wrappers" | ||
}, | ||
@@ -33,2 +39,3 @@ | ||
create(context) { | ||
const { sourceCode } = context; | ||
@@ -39,9 +46,14 @@ return { | ||
const wrapperObjects = ["String", "Number", "Boolean"]; | ||
const { name } = node.callee; | ||
if (wrapperObjects.includes(node.callee.name)) { | ||
context.report({ | ||
node, | ||
messageId: "noConstructor", | ||
data: { fn: node.callee.name } | ||
}); | ||
if (wrapperObjects.includes(name)) { | ||
const variable = getVariableByName(sourceCode.getScope(node), name); | ||
if (variable && variable.identifiers.length === 0) { | ||
context.report({ | ||
node, | ||
messageId: "noConstructor", | ||
data: { fn: name } | ||
}); | ||
} | ||
} | ||
@@ -48,0 +60,0 @@ } |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-new" | ||
url: "https://eslint.org/docs/latest/rules/no-new" | ||
}, | ||
@@ -24,0 +24,0 @@ |
@@ -35,3 +35,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape" | ||
url: "https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape" | ||
}, | ||
@@ -53,3 +53,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -56,0 +56,0 @@ /** |
@@ -12,3 +12,3 @@ /** | ||
const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils"); | ||
const { CALL, CONSTRUCT, ReferenceTracker } = require("@eslint-community/eslint-utils"); | ||
const getPropertyName = require("./utils/ast-utils").getStaticPropertyName; | ||
@@ -49,3 +49,3 @@ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-obj-calls" | ||
url: "https://eslint.org/docs/latest/rules/no-obj-calls" | ||
}, | ||
@@ -63,5 +63,7 @@ | ||
const sourceCode = context.sourceCode; | ||
return { | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
const tracker = new ReferenceTracker(scope); | ||
@@ -77,8 +79,8 @@ const traceMap = {}; | ||
for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) { | ||
const name = getReportNodeName(node.callee); | ||
for (const { node: refNode, path } of tracker.iterateGlobalReferences(traceMap)) { | ||
const name = getReportNodeName(refNode.callee); | ||
const ref = path[0]; | ||
const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall"; | ||
context.report({ node, messageId, data: { name, ref } }); | ||
context.report({ node: refNode, messageId, data: { name, ref } }); | ||
} | ||
@@ -85,0 +87,0 @@ } |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-octal-escape" | ||
url: "https://eslint.org/docs/latest/rules/no-octal-escape" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-octal" | ||
url: "https://eslint.org/docs/latest/rules/no-octal" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-param-reassign" | ||
url: "https://eslint.org/docs/latest/rules/no-param-reassign" | ||
}, | ||
@@ -74,2 +74,3 @@ | ||
const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || []; | ||
const sourceCode = context.sourceCode; | ||
@@ -219,3 +220,3 @@ /** | ||
function checkForFunction(node) { | ||
context.getDeclaredVariables(node).forEach(checkVariable); | ||
sourceCode.getDeclaredVariables(node).forEach(checkVariable); | ||
} | ||
@@ -222,0 +223,0 @@ |
@@ -24,3 +24,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-path-concat" | ||
url: "https://eslint.org/docs/latest/rules/no-path-concat" | ||
}, | ||
@@ -27,0 +27,0 @@ |
@@ -56,3 +56,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-plusplus" | ||
url: "https://eslint.org/docs/latest/rules/no-plusplus" | ||
}, | ||
@@ -59,0 +59,0 @@ |
@@ -24,3 +24,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-process-env" | ||
url: "https://eslint.org/docs/latest/rules/no-process-env" | ||
}, | ||
@@ -27,0 +27,0 @@ |
@@ -24,3 +24,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-process-exit" | ||
url: "https://eslint.org/docs/latest/rules/no-process-exit" | ||
}, | ||
@@ -27,0 +27,0 @@ |
@@ -12,3 +12,4 @@ /** | ||
const { findVariable } = require("eslint-utils"); | ||
const { findVariable } = require("@eslint-community/eslint-utils"); | ||
const astUtils = require("./utils/ast-utils"); | ||
@@ -63,2 +64,74 @@ //------------------------------------------------------------------------------ | ||
/** | ||
* Checks if the given node is a void expression. | ||
* @param {ASTNode} node The node to check. | ||
* @returns {boolean} - `true` if the node is a void expression | ||
*/ | ||
function expressionIsVoid(node) { | ||
return node.type === "UnaryExpression" && node.operator === "void"; | ||
} | ||
/** | ||
* Fixes the linting error by prepending "void " to the given node | ||
* @param {Object} sourceCode context given by context.sourceCode | ||
* @param {ASTNode} node The node to fix. | ||
* @param {Object} fixer The fixer object provided by ESLint. | ||
* @returns {Array<Object>} - An array of fix objects to apply to the node. | ||
*/ | ||
function voidPrependFixer(sourceCode, node, fixer) { | ||
const requiresParens = | ||
// prepending `void ` will fail if the node has a lower precedence than void | ||
astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) && | ||
// check if there are parentheses around the node to avoid redundant parentheses | ||
!astUtils.isParenthesised(sourceCode, node); | ||
// avoid parentheses issues | ||
const returnOrArrowToken = sourceCode.getTokenBefore( | ||
node, | ||
node.parent.type === "ArrowFunctionExpression" | ||
? astUtils.isArrowToken | ||
// isReturnToken | ||
: token => token.type === "Keyword" && token.value === "return" | ||
); | ||
const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); | ||
const prependSpace = | ||
// is return token, as => allows void to be adjacent | ||
returnOrArrowToken.value === "return" && | ||
// If two tokens (return and "(") are adjacent | ||
returnOrArrowToken.range[1] === firstToken.range[0]; | ||
return [ | ||
fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`), | ||
fixer.insertTextAfter(node, requiresParens ? ")" : "") | ||
]; | ||
} | ||
/** | ||
* Fixes the linting error by `wrapping {}` around the given node's body. | ||
* @param {Object} sourceCode context given by context.sourceCode | ||
* @param {ASTNode} node The node to fix. | ||
* @param {Object} fixer The fixer object provided by ESLint. | ||
* @returns {Array<Object>} - An array of fix objects to apply to the node. | ||
*/ | ||
function curlyWrapFixer(sourceCode, node, fixer) { | ||
// https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923 | ||
const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); | ||
const firstToken = sourceCode.getTokenAfter(arrowToken); | ||
const lastToken = sourceCode.getLastToken(node); | ||
return [ | ||
fixer.insertTextBefore(firstToken, "{"), | ||
fixer.insertTextAfter(lastToken, "}") | ||
]; | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -76,9 +149,26 @@ // Rule Definition | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-promise-executor-return" | ||
url: "https://eslint.org/docs/latest/rules/no-promise-executor-return" | ||
}, | ||
schema: [], | ||
hasSuggestions: true, | ||
schema: [{ | ||
type: "object", | ||
properties: { | ||
allowVoid: { | ||
type: "boolean", | ||
default: false | ||
} | ||
}, | ||
additionalProperties: false | ||
}], | ||
messages: { | ||
returnsValue: "Return values from promise executor functions cannot be read." | ||
returnsValue: "Return values from promise executor functions cannot be read.", | ||
// arrow and function suggestions | ||
prependVoid: "Prepend `void` to the expression.", | ||
// only arrow suggestions | ||
wrapBraces: "Wrap the expression in `{}`." | ||
} | ||
@@ -90,12 +180,7 @@ }, | ||
let funcInfo = null; | ||
const sourceCode = context.sourceCode; | ||
const { | ||
allowVoid = false | ||
} = context.options[0] || {}; | ||
/** | ||
* Reports the given node. | ||
* @param {ASTNode} node Node to report. | ||
* @returns {void} | ||
*/ | ||
function report(node) { | ||
context.report({ node, messageId: "returnsValue" }); | ||
} | ||
return { | ||
@@ -106,7 +191,39 @@ | ||
upper: funcInfo, | ||
shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, context.getScope()) | ||
shouldCheck: | ||
functionTypesToCheck.has(node.type) && | ||
isPromiseExecutor(node, sourceCode.getScope(node)) | ||
}; | ||
if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) { | ||
report(node.body); | ||
if (// Is a Promise executor | ||
funcInfo.shouldCheck && | ||
node.type === "ArrowFunctionExpression" && | ||
node.expression && | ||
// Except void | ||
!(allowVoid && expressionIsVoid(node.body)) | ||
) { | ||
const suggest = []; | ||
// prevent useless refactors | ||
if (allowVoid) { | ||
suggest.push({ | ||
messageId: "prependVoid", | ||
fix(fixer) { | ||
return voidPrependFixer(sourceCode, node.body, fixer); | ||
} | ||
}); | ||
} | ||
suggest.push({ | ||
messageId: "wrapBraces", | ||
fix(fixer) { | ||
return curlyWrapFixer(sourceCode, node, fixer); | ||
} | ||
}); | ||
context.report({ | ||
node: node.body, | ||
messageId: "returnsValue", | ||
suggest | ||
}); | ||
} | ||
@@ -120,5 +237,27 @@ }, | ||
ReturnStatement(node) { | ||
if (funcInfo.shouldCheck && node.argument) { | ||
report(node); | ||
if (!(funcInfo.shouldCheck && node.argument)) { | ||
return; | ||
} | ||
// node is `return <expression>` | ||
if (!allowVoid) { | ||
context.report({ node, messageId: "returnsValue" }); | ||
return; | ||
} | ||
if (expressionIsVoid(node.argument)) { | ||
return; | ||
} | ||
// allowVoid && !expressionIsVoid | ||
context.report({ | ||
node, | ||
messageId: "returnsValue", | ||
suggest: [{ | ||
messageId: "prependVoid", | ||
fix(fixer) { | ||
return voidPrependFixer(sourceCode, node.argument, fixer); | ||
} | ||
}] | ||
}); | ||
} | ||
@@ -125,0 +264,0 @@ }; |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-proto" | ||
url: "https://eslint.org/docs/latest/rules/no-proto" | ||
}, | ||
@@ -29,0 +29,0 @@ |
@@ -25,3 +25,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-prototype-builtins" | ||
url: "https://eslint.org/docs/latest/rules/no-prototype-builtins" | ||
}, | ||
@@ -28,0 +28,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-redeclare" | ||
url: "https://eslint.org/docs/latest/rules/no-redeclare" | ||
}, | ||
@@ -54,3 +54,3 @@ | ||
}; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -134,3 +134,3 @@ /** | ||
function checkForBlock(node) { | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
@@ -147,4 +147,4 @@ /* | ||
return { | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
@@ -151,0 +151,0 @@ findVariablesInScope(scope); |
@@ -13,3 +13,3 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const regexpp = require("regexpp"); | ||
const regexpp = require("@eslint-community/regexpp"); | ||
@@ -45,3 +45,3 @@ //------------------------------------------------------------------------------ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-regex-spaces" | ||
url: "https://eslint.org/docs/latest/rules/no-regex-spaces" | ||
}, | ||
@@ -59,2 +59,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -81,3 +83,3 @@ * Validate regular expression | ||
try { | ||
regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); | ||
regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); | ||
} catch { | ||
@@ -156,7 +158,6 @@ | ||
function checkFunction(node) { | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
const regExpVar = astUtils.getVariableByName(scope, "RegExp"); | ||
const shadowed = regExpVar && regExpVar.defs.length > 0; | ||
const patternNode = node.arguments[0]; | ||
const flagsNode = node.arguments[1]; | ||
@@ -167,4 +168,20 @@ if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) { | ||
const rawPatternStartRange = patternNode.range[0] + 1; | ||
const flags = isString(flagsNode) ? flagsNode.value : ""; | ||
let flags; | ||
if (node.arguments.length < 2) { | ||
// It has no flags. | ||
flags = ""; | ||
} else { | ||
const flagsNode = node.arguments[1]; | ||
if (isString(flagsNode)) { | ||
flags = flagsNode.value; | ||
} else { | ||
// The flags cannot be determined. | ||
return; | ||
} | ||
} | ||
checkRegex( | ||
@@ -171,0 +188,0 @@ node, |
@@ -26,21 +26,71 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-restricted-exports" | ||
url: "https://eslint.org/docs/latest/rules/no-restricted-exports" | ||
}, | ||
schema: [{ | ||
type: "object", | ||
properties: { | ||
restrictedNamedExports: { | ||
type: "array", | ||
items: { | ||
type: "string" | ||
anyOf: [ | ||
{ | ||
type: "object", | ||
properties: { | ||
restrictedNamedExports: { | ||
type: "array", | ||
items: { | ||
type: "string" | ||
}, | ||
uniqueItems: true | ||
} | ||
}, | ||
uniqueItems: true | ||
additionalProperties: false | ||
}, | ||
{ | ||
type: "object", | ||
properties: { | ||
restrictedNamedExports: { | ||
type: "array", | ||
items: { | ||
type: "string", | ||
pattern: "^(?!default$)" | ||
}, | ||
uniqueItems: true | ||
}, | ||
restrictDefaultExports: { | ||
type: "object", | ||
properties: { | ||
// Allow/Disallow `export default foo; export default 42; export default function foo() {}` format | ||
direct: { | ||
type: "boolean" | ||
}, | ||
// Allow/Disallow `export { foo as default };` declarations | ||
named: { | ||
type: "boolean" | ||
}, | ||
// Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations | ||
defaultFrom: { | ||
type: "boolean" | ||
}, | ||
// Allow/Disallow `export { foo as default } from "mod";` declarations | ||
namedFrom: { | ||
type: "boolean" | ||
}, | ||
// Allow/Disallow `export * as default from "mod"`; declarations | ||
namespaceFrom: { | ||
type: "boolean" | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
}, | ||
additionalProperties: false | ||
] | ||
}], | ||
messages: { | ||
restrictedNamed: "'{{name}}' is restricted from being used as an exported name." | ||
restrictedNamed: "'{{name}}' is restricted from being used as an exported name.", | ||
restrictedDefault: "Exporting 'default' is restricted." | ||
} | ||
@@ -52,2 +102,4 @@ }, | ||
const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); | ||
const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; | ||
const sourceCode = context.sourceCode; | ||
@@ -68,3 +120,39 @@ /** | ||
}); | ||
return; | ||
} | ||
if (name === "default") { | ||
if (node.parent.type === "ExportAllDeclaration") { | ||
if (restrictDefaultExports && restrictDefaultExports.namespaceFrom) { | ||
context.report({ | ||
node, | ||
messageId: "restrictedDefault" | ||
}); | ||
} | ||
} else { // ExportSpecifier | ||
const isSourceSpecified = !!node.parent.parent.source; | ||
const specifierLocalName = astUtils.getModuleExportName(node.parent.local); | ||
if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { | ||
context.report({ | ||
node, | ||
messageId: "restrictedDefault" | ||
}); | ||
return; | ||
} | ||
if (isSourceSpecified && restrictDefaultExports) { | ||
if ( | ||
(specifierLocalName === "default" && restrictDefaultExports.defaultFrom) || | ||
(specifierLocalName !== "default" && restrictDefaultExports.namedFrom) | ||
) { | ||
context.report({ | ||
node, | ||
messageId: "restrictedDefault" | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
@@ -79,2 +167,11 @@ | ||
ExportDefaultDeclaration(node) { | ||
if (restrictDefaultExports && restrictDefaultExports.direct) { | ||
context.report({ | ||
node, | ||
messageId: "restrictedDefault" | ||
}); | ||
} | ||
}, | ||
ExportNamedDeclaration(node) { | ||
@@ -87,3 +184,3 @@ const declaration = node.declaration; | ||
} else if (declaration.type === "VariableDeclaration") { | ||
context.getDeclaredVariables(declaration) | ||
sourceCode.getDeclaredVariables(declaration) | ||
.map(v => v.defs.find(d => d.parent === declaration)) | ||
@@ -90,0 +187,0 @@ .map(d => d.name) // Identifier nodes |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-restricted-globals" | ||
url: "https://eslint.org/docs/latest/rules/no-restricted-globals" | ||
}, | ||
@@ -54,2 +54,4 @@ | ||
const sourceCode = context.sourceCode; | ||
// If no globals are restricted, we don't need to do anything | ||
@@ -104,4 +106,4 @@ if (context.options.length === 0) { | ||
return { | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
@@ -108,0 +110,0 @@ // Report variables declared elsewhere (ex: variables defined as "global" by eslint) |
@@ -101,3 +101,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-restricted-imports" | ||
url: "https://eslint.org/docs/latest/rules/no-restricted-imports" | ||
}, | ||
@@ -151,3 +151,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const options = Array.isArray(context.options) ? context.options : []; | ||
@@ -154,0 +154,0 @@ const isPathAndPatternsObject = |
@@ -9,2 +9,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const astUtils = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -55,3 +61,3 @@ //------------------------------------------------------------------------------ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-restricted-modules" | ||
url: "https://eslint.org/docs/latest/rules/no-restricted-modules" | ||
}, | ||
@@ -122,11 +128,2 @@ | ||
/** | ||
* Function to check if a node is a static string template literal. | ||
* @param {ASTNode} node The node to check. | ||
* @returns {boolean} If the node is a string template literal. | ||
*/ | ||
function isStaticTemplateLiteral(node) { | ||
return node && node.type === "TemplateLiteral" && node.expressions.length === 0; | ||
} | ||
/** | ||
* Function to check if a node is a require call. | ||
@@ -150,3 +147,3 @@ * @param {ASTNode} node The node to check. | ||
if (isStaticTemplateLiteral(node)) { | ||
if (astUtils.isStaticTemplateLiteral(node)) { | ||
return node.quasis[0].value.cooked.trim(); | ||
@@ -153,0 +150,0 @@ } |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-restricted-properties" | ||
url: "https://eslint.org/docs/latest/rules/no-restricted-properties" | ||
}, | ||
@@ -25,0 +25,0 @@ |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-restricted-syntax" | ||
url: "https://eslint.org/docs/latest/rules/no-restricted-syntax" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -31,3 +31,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-return-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-return-assign" | ||
}, | ||
@@ -49,3 +49,3 @@ | ||
const always = (context.options[0] || "except-parens") !== "except-parens"; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -52,0 +52,0 @@ return { |
/** | ||
* @fileoverview Disallows unnecessary `return await` | ||
* @author Jordan Harband | ||
* @deprecated in ESLint v8.46.0 | ||
*/ | ||
@@ -16,2 +17,3 @@ "use strict"; | ||
meta: { | ||
hasSuggestions: true, | ||
type: "suggestion", | ||
@@ -24,3 +26,3 @@ | ||
url: "https://eslint.org/docs/rules/no-return-await" | ||
url: "https://eslint.org/docs/latest/rules/no-return-await" | ||
}, | ||
@@ -30,2 +32,6 @@ | ||
deprecated: true, | ||
replacedBy: [], | ||
schema: [ | ||
@@ -35,2 +41,3 @@ ], | ||
messages: { | ||
removeAwait: "Remove redundant `await`.", | ||
redundantUseOfAwait: "Redundant use of `await` on a return value." | ||
@@ -49,5 +56,30 @@ } | ||
context.report({ | ||
node: context.getSourceCode().getFirstToken(node), | ||
node: context.sourceCode.getFirstToken(node), | ||
loc: node.loc, | ||
messageId: "redundantUseOfAwait" | ||
messageId: "redundantUseOfAwait", | ||
suggest: [ | ||
{ | ||
messageId: "removeAwait", | ||
fix(fixer) { | ||
const sourceCode = context.sourceCode; | ||
const [awaitToken, tokenAfterAwait] = sourceCode.getFirstTokens(node, 2); | ||
const areAwaitAndAwaitedExpressionOnTheSameLine = awaitToken.loc.start.line === tokenAfterAwait.loc.start.line; | ||
if (!areAwaitAndAwaitedExpressionOnTheSameLine) { | ||
return null; | ||
} | ||
const [startOfAwait, endOfAwait] = awaitToken.range; | ||
const characterAfterAwait = sourceCode.text[endOfAwait]; | ||
const trimLength = characterAfterAwait === " " ? 1 : 0; | ||
const range = [startOfAwait, endOfAwait + trimLength]; | ||
return fixer.removeRange(range); | ||
} | ||
} | ||
] | ||
}); | ||
@@ -54,0 +86,0 @@ } |
@@ -23,3 +23,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-script-url" | ||
url: "https://eslint.org/docs/latest/rules/no-script-url" | ||
}, | ||
@@ -26,0 +26,0 @@ |
@@ -135,3 +135,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-self-assign" | ||
url: "https://eslint.org/docs/latest/rules/no-self-assign" | ||
}, | ||
@@ -158,3 +158,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const [{ props = true } = {}] = context.options; | ||
@@ -161,0 +161,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-self-compare" | ||
url: "https://eslint.org/docs/latest/rules/no-self-compare" | ||
}, | ||
@@ -33,3 +33,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -36,0 +36,0 @@ /** |
@@ -34,3 +34,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-sequences" | ||
url: "https://eslint.org/docs/latest/rules/no-sequences" | ||
}, | ||
@@ -55,3 +55,3 @@ | ||
const options = Object.assign({}, DEFAULT_OPTIONS, context.options[0]); | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -58,0 +58,0 @@ /** |
@@ -13,3 +13,3 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const { findVariable } = require("eslint-utils"); | ||
const { findVariable } = require("@eslint-community/eslint-utils"); | ||
@@ -148,3 +148,3 @@ //------------------------------------------------------------------------------ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-setter-return" | ||
url: "https://eslint.org/docs/latest/rules/no-setter-return" | ||
}, | ||
@@ -161,2 +161,3 @@ | ||
let funcInfo = null; | ||
const sourceCode = context.sourceCode; | ||
@@ -169,3 +170,3 @@ /** | ||
function enterFunction(node) { | ||
const outerScope = getOuterScope(context.getScope()); | ||
const outerScope = getOuterScope(sourceCode.getScope(node)); | ||
@@ -172,0 +173,0 @@ funcInfo = { |
@@ -32,3 +32,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-shadow-restricted-names" | ||
url: "https://eslint.org/docs/latest/rules/no-shadow-restricted-names" | ||
}, | ||
@@ -47,6 +47,7 @@ | ||
const RESTRICTED = new Set(["undefined", "NaN", "Infinity", "arguments", "eval"]); | ||
const sourceCode = context.sourceCode; | ||
return { | ||
"VariableDeclaration, :function, CatchClause"(node) { | ||
for (const variable of context.getDeclaredVariables(node)) { | ||
for (const variable of sourceCode.getDeclaredVariables(node)) { | ||
if (variable.defs.length > 0 && RESTRICTED.has(variable.name) && !safelyShadowsUndefined(variable)) { | ||
@@ -53,0 +54,0 @@ context.report({ |
@@ -35,3 +35,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-shadow" | ||
url: "https://eslint.org/docs/latest/rules/no-shadow" | ||
}, | ||
@@ -71,2 +71,3 @@ | ||
}; | ||
const sourceCode = context.sourceCode; | ||
@@ -323,4 +324,4 @@ /** | ||
return { | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
const stack = globalScope.childScopes.slice(); | ||
@@ -327,0 +328,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-spaced-func" | ||
url: "https://eslint.org/docs/latest/rules/no-spaced-func" | ||
}, | ||
@@ -39,3 +39,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -42,0 +42,0 @@ /** |
@@ -19,3 +19,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-sparse-arrays" | ||
url: "https://eslint.org/docs/latest/rules/no-sparse-arrays" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-sync" | ||
url: "https://eslint.org/docs/latest/rules/no-sync" | ||
}, | ||
@@ -28,0 +28,0 @@ |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-tabs" | ||
url: "https://eslint.org/docs/latest/rules/no-tabs" | ||
}, | ||
@@ -47,3 +47,3 @@ schema: [{ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs; | ||
@@ -50,0 +50,0 @@ |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-template-curly-in-string" | ||
url: "https://eslint.org/docs/latest/rules/no-template-curly-in-string" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-ternary" | ||
url: "https://eslint.org/docs/latest/rules/no-ternary" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -45,3 +45,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-this-before-super" | ||
url: "https://eslint.org/docs/latest/rules/no-this-before-super" | ||
}, | ||
@@ -95,2 +95,17 @@ | ||
/** | ||
* Determines if every segment in a set has been called. | ||
* @param {Set<CodePathSegment>} segments The segments to search. | ||
* @returns {boolean} True if every segment has been called; false otherwise. | ||
*/ | ||
function isEverySegmentCalled(segments) { | ||
for (const segment of segments) { | ||
if (!isCalled(segment)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
/** | ||
* Checks whether or not this is before `super()` is called. | ||
@@ -102,3 +117,3 @@ * @returns {boolean} `true` if this is before `super()` is called. | ||
isInConstructorOfDerivedClass() && | ||
!funcInfo.codePath.currentSegments.every(isCalled) | ||
!isEverySegmentCalled(funcInfo.currentSegments) | ||
); | ||
@@ -114,7 +129,5 @@ } | ||
function setInvalid(node) { | ||
const segments = funcInfo.codePath.currentSegments; | ||
const segments = funcInfo.currentSegments; | ||
for (let i = 0; i < segments.length; ++i) { | ||
const segment = segments[i]; | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
@@ -131,7 +144,5 @@ segInfoMap[segment.id].invalidNodes.push(node); | ||
function setSuperCalled() { | ||
const segments = funcInfo.codePath.currentSegments; | ||
const segments = funcInfo.currentSegments; | ||
for (let i = 0; i < segments.length; ++i) { | ||
const segment = segments[i]; | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
@@ -164,3 +175,4 @@ segInfoMap[segment.id].superCalled = true; | ||
), | ||
codePath | ||
codePath, | ||
currentSegments: new Set() | ||
}; | ||
@@ -172,3 +184,4 @@ } else { | ||
hasExtends: false, | ||
codePath | ||
codePath, | ||
currentSegments: new Set() | ||
}; | ||
@@ -221,2 +234,4 @@ } | ||
onCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
if (!isInConstructorOfDerivedClass()) { | ||
@@ -236,2 +251,14 @@ return; | ||
onUnreachableCodePathSegmentStart(segment) { | ||
funcInfo.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
funcInfo.currentSegments.delete(segment); | ||
}, | ||
/** | ||
@@ -238,0 +265,0 @@ * Update information of the code path segment when a code path was |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-throw-literal" | ||
url: "https://eslint.org/docs/latest/rules/no-throw-literal" | ||
}, | ||
@@ -25,0 +25,0 @@ |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-trailing-spaces" | ||
url: "https://eslint.org/docs/latest/rules/no-trailing-spaces" | ||
}, | ||
@@ -54,3 +54,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -57,0 +57,0 @@ const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]", |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-undef-init" | ||
url: "https://eslint.org/docs/latest/rules/no-undef-init" | ||
}, | ||
@@ -36,3 +36,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -44,3 +44,3 @@ return { | ||
init = node.init && node.init.name, | ||
scope = context.getScope(), | ||
scope = sourceCode.getScope(node), | ||
undefinedVar = astUtils.getVariableByName(scope, "undefined"), | ||
@@ -47,0 +47,0 @@ shadowed = undefinedVar && undefinedVar.defs.length > 0, |
@@ -34,3 +34,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-undef" | ||
url: "https://eslint.org/docs/latest/rules/no-undef" | ||
}, | ||
@@ -58,6 +58,7 @@ | ||
const considerTypeOf = options && options.typeof === true || false; | ||
const sourceCode = context.sourceCode; | ||
return { | ||
"Program:exit"(/* node */) { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -64,0 +65,0 @@ globalScope.through.forEach(ref => { |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-undefined" | ||
url: "https://eslint.org/docs/latest/rules/no-undefined" | ||
}, | ||
@@ -32,2 +32,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -71,4 +73,4 @@ * Report an invalid "undefined" identifier node. | ||
return { | ||
"Program:exit"() { | ||
const globalScope = context.getScope(); | ||
"Program:exit"(node) { | ||
const globalScope = sourceCode.getScope(node); | ||
@@ -75,0 +77,0 @@ const stack = [globalScope]; |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-underscore-dangle" | ||
url: "https://eslint.org/docs/latest/rules/no-underscore-dangle" | ||
}, | ||
@@ -57,2 +57,10 @@ | ||
default: false | ||
}, | ||
allowInArrayDestructuring: { | ||
type: "boolean", | ||
default: true | ||
}, | ||
allowInObjectDestructuring: { | ||
type: "boolean", | ||
default: true | ||
} | ||
@@ -79,2 +87,5 @@ }, | ||
const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true; | ||
const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true; | ||
const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true; | ||
const sourceCode = context.sourceCode; | ||
@@ -201,2 +212,3 @@ //------------------------------------------------------------------------- | ||
/** | ||
@@ -209,14 +221,28 @@ * Check if variable expression has a dangling underscore | ||
function checkForDanglingUnderscoreInVariableExpression(node) { | ||
const identifier = node.id.name; | ||
sourceCode.getDeclaredVariables(node).forEach(variable => { | ||
const definition = variable.defs.find(def => def.node === node); | ||
const identifierNode = definition.name; | ||
const identifier = identifierNode.name; | ||
let parent = identifierNode.parent; | ||
if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && | ||
!isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { | ||
context.report({ | ||
node, | ||
messageId: "unexpectedUnderscore", | ||
data: { | ||
identifier | ||
} | ||
}); | ||
} | ||
while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) { | ||
parent = parent.parent; | ||
} | ||
if ( | ||
hasDanglingUnderscore(identifier) && | ||
!isSpecialCaseIdentifierInVariableExpression(identifier) && | ||
!isAllowed(identifier) && | ||
!(allowInArrayDestructuring && parent.type === "ArrayPattern") && | ||
!(allowInObjectDestructuring && parent.type === "ObjectPattern") | ||
) { | ||
context.report({ | ||
node, | ||
messageId: "unexpectedUnderscore", | ||
data: { | ||
identifier | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
@@ -223,0 +249,0 @@ |
@@ -25,3 +25,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-unexpected-multiline" | ||
url: "https://eslint.org/docs/latest/rules/no-unexpected-multiline" | ||
}, | ||
@@ -42,3 +42,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -45,0 +45,0 @@ /** |
@@ -167,3 +167,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-unmodified-loop-condition" | ||
url: "https://eslint.org/docs/latest/rules/no-unmodified-loop-condition" | ||
}, | ||
@@ -179,3 +179,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let groupMap = null; | ||
@@ -345,4 +345,4 @@ | ||
return { | ||
"Program:exit"() { | ||
const queue = [context.getScope()]; | ||
"Program:exit"(node) { | ||
const queue = [sourceCode.getScope(node)]; | ||
@@ -349,0 +349,0 @@ groupMap = new Map(); |
@@ -34,3 +34,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-unneeded-ternary" | ||
url: "https://eslint.org/docs/latest/rules/no-unneeded-ternary" | ||
}, | ||
@@ -62,3 +62,3 @@ | ||
const defaultAssignment = options.defaultAssignment !== false; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -149,3 +149,3 @@ /** | ||
messageId: "unnecessaryConditionalAssignment", | ||
fix: fixer => { | ||
fix(fixer) { | ||
const shouldParenthesizeAlternate = | ||
@@ -152,0 +152,0 @@ ( |
@@ -15,2 +15,18 @@ /** | ||
/** | ||
* Checks all segments in a set and returns true if any are reachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if any segment is reachable; false otherwise. | ||
*/ | ||
function isAnySegmentReachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Determines whether the given node is the first node in the code path to which a loop statement | ||
@@ -65,3 +81,3 @@ * 'loops' for the next iteration. | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-unreachable-loop" | ||
url: "https://eslint.org/docs/latest/rules/no-unreachable-loop" | ||
}, | ||
@@ -95,25 +111,32 @@ | ||
let currentCodePath = null; | ||
const codePathSegments = []; | ||
let currentCodePathSegments = new Set(); | ||
return { | ||
onCodePathStart(codePath) { | ||
currentCodePath = codePath; | ||
onCodePathStart() { | ||
codePathSegments.push(currentCodePathSegments); | ||
currentCodePathSegments = new Set(); | ||
}, | ||
onCodePathEnd() { | ||
currentCodePath = currentCodePath.upper; | ||
currentCodePathSegments = codePathSegments.pop(); | ||
}, | ||
[loopSelector](node) { | ||
onUnreachableCodePathSegmentStart(segment) { | ||
currentCodePathSegments.add(segment); | ||
}, | ||
/** | ||
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise. | ||
* For unreachable segments, the code path analysis does not raise events required for this implementation. | ||
*/ | ||
if (currentCodePath.currentSegments.some(segment => segment.reachable)) { | ||
loopsToReport.add(node); | ||
} | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
currentCodePathSegments.delete(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
currentCodePathSegments.delete(segment); | ||
}, | ||
onCodePathSegmentStart(segment, node) { | ||
currentCodePathSegments.add(segment); | ||
if (isLoopingTarget(node)) { | ||
@@ -146,2 +169,14 @@ const loop = node.parent; | ||
[loopSelector](node) { | ||
/** | ||
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise. | ||
* For unreachable segments, the code path analysis does not raise events required for this implementation. | ||
*/ | ||
if (isAnySegmentReachable(currentCodePathSegments)) { | ||
loopsToReport.add(node); | ||
} | ||
}, | ||
"Program:exit"() { | ||
@@ -148,0 +183,0 @@ loopsToReport.forEach( |
@@ -27,8 +27,15 @@ /** | ||
/** | ||
* Checks whether or not a given code path segment is unreachable. | ||
* @param {CodePathSegment} segment A CodePathSegment to check. | ||
* @returns {boolean} `true` if the segment is unreachable. | ||
* Checks all segments in a set and returns true if all are unreachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if all segments are unreachable; false otherwise. | ||
*/ | ||
function isUnreachable(segment) { | ||
return !segment.reachable; | ||
function areAllSegmentsUnreachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
@@ -117,3 +124,3 @@ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-unreachable" | ||
url: "https://eslint.org/docs/latest/rules/no-unreachable" | ||
}, | ||
@@ -129,3 +136,2 @@ | ||
create(context) { | ||
let currentCodePath = null; | ||
@@ -136,4 +142,10 @@ /** @type {ConstructorInfo | null} */ | ||
/** @type {ConsecutiveRange} */ | ||
const range = new ConsecutiveRange(context.getSourceCode()); | ||
const range = new ConsecutiveRange(context.sourceCode); | ||
/** @type {Array<Set<CodePathSegment>>} */ | ||
const codePathSegments = []; | ||
/** @type {Set<CodePathSegment>} */ | ||
let currentCodePathSegments = new Set(); | ||
/** | ||
@@ -147,3 +159,3 @@ * Reports a given node if it's unreachable. | ||
if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) { | ||
if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) { | ||
@@ -189,10 +201,27 @@ // Store this statement to distinguish consecutive statements. | ||
// Manages the current code path. | ||
onCodePathStart(codePath) { | ||
currentCodePath = codePath; | ||
onCodePathStart() { | ||
codePathSegments.push(currentCodePathSegments); | ||
currentCodePathSegments = new Set(); | ||
}, | ||
onCodePathEnd() { | ||
currentCodePath = currentCodePath.upper; | ||
currentCodePathSegments = codePathSegments.pop(); | ||
}, | ||
onUnreachableCodePathSegmentStart(segment) { | ||
currentCodePathSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
currentCodePathSegments.delete(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
currentCodePathSegments.delete(segment); | ||
}, | ||
onCodePathSegmentStart(segment) { | ||
currentCodePathSegments.add(segment); | ||
}, | ||
// Registers for all statement nodes (excludes FunctionDeclaration). | ||
@@ -199,0 +228,0 @@ BlockStatement: reportIfUnreachable, |
@@ -29,3 +29,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-unsafe-finally" | ||
url: "https://eslint.org/docs/latest/rules/no-unsafe-finally" | ||
}, | ||
@@ -32,0 +32,0 @@ |
@@ -57,3 +57,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-unsafe-negation" | ||
url: "https://eslint.org/docs/latest/rules/no-unsafe-negation" | ||
}, | ||
@@ -86,3 +86,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const options = context.options[0] || {}; | ||
@@ -89,0 +89,0 @@ const enforceForOrderingRelations = options.enforceForOrderingRelations === true; |
@@ -29,3 +29,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-unsafe-optional-chaining" | ||
url: "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining" | ||
}, | ||
@@ -32,0 +32,0 @@ schema: [{ |
@@ -7,2 +7,4 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
@@ -36,3 +38,3 @@ // Rule Definition | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-unused-expressions" | ||
url: "https://eslint.org/docs/latest/rules/no-unused-expressions" | ||
}, | ||
@@ -114,8 +116,5 @@ | ||
* @param {ASTNode} node any node | ||
* @param {ASTNode[]} ancestors the given node's ancestors | ||
* @returns {boolean} whether the given node is considered a directive in its current position | ||
*/ | ||
function isDirective(node, ancestors) { | ||
const parent = ancestors[ancestors.length - 1], | ||
grandparent = ancestors[ancestors.length - 2]; | ||
function isDirective(node) { | ||
@@ -128,5 +127,3 @@ /** | ||
*/ | ||
return (parent.type === "Program" || parent.type === "BlockStatement" && | ||
(/Function/u.test(grandparent.type))) && | ||
directives(parent).includes(node); | ||
return astUtils.isTopLevelExpressionStatement(node) && directives(node.parent).includes(node); | ||
} | ||
@@ -187,3 +184,3 @@ | ||
ExpressionStatement(node) { | ||
if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) { | ||
if (Checker.isDisallowed(node.expression) && !isDirective(node)) { | ||
context.report({ node, messageId: "unusedExpression" }); | ||
@@ -190,0 +187,0 @@ } |
@@ -9,2 +9,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const astUtils = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -21,3 +27,3 @@ //------------------------------------------------------------------------------ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-unused-labels" | ||
url: "https://eslint.org/docs/latest/rules/no-unused-labels" | ||
}, | ||
@@ -35,3 +41,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let scopeInfo = null; | ||
@@ -53,2 +59,41 @@ | ||
/** | ||
* Checks if a `LabeledStatement` node is fixable. | ||
* For a node to be fixable, there must be no comments between the label and the body. | ||
* Furthermore, is must be possible to remove the label without turning the body statement into a | ||
* directive after other fixes are applied. | ||
* @param {ASTNode} node The node to evaluate. | ||
* @returns {boolean} Whether or not the node is fixable. | ||
*/ | ||
function isFixable(node) { | ||
/* | ||
* Only perform a fix if there are no comments between the label and the body. This will be the case | ||
* when there is exactly one token/comment (the ":") between the label and the body. | ||
*/ | ||
if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !== | ||
sourceCode.getTokenBefore(node.body, { includeComments: true })) { | ||
return false; | ||
} | ||
// Looking for the node's deepest ancestor which is not a `LabeledStatement`. | ||
let ancestor = node.parent; | ||
while (ancestor.type === "LabeledStatement") { | ||
ancestor = ancestor.parent; | ||
} | ||
if (ancestor.type === "Program" || | ||
(ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) { | ||
const { body } = node; | ||
if (body.type === "ExpressionStatement" && | ||
((body.expression.type === "Literal" && typeof body.expression.value === "string") || | ||
astUtils.isStaticTemplateLiteral(body.expression))) { | ||
return false; // potential directive | ||
} | ||
} | ||
return true; | ||
} | ||
/** | ||
* Removes the top of the stack. | ||
@@ -65,15 +110,3 @@ * At the same time, this reports the label if it's never used. | ||
data: node.label, | ||
fix(fixer) { | ||
/* | ||
* Only perform a fix if there are no comments between the label and the body. This will be the case | ||
* when there is exactly one token/comment (the ":") between the label and the body. | ||
*/ | ||
if (sourceCode.getTokenAfter(node.label, { includeComments: true }) === | ||
sourceCode.getTokenBefore(node.body, { includeComments: true })) { | ||
return fixer.removeRange([node.range[0], node.body.range[0]]); | ||
} | ||
return null; | ||
} | ||
fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null | ||
}); | ||
@@ -80,0 +113,0 @@ } |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-unused-private-class-members" | ||
url: "https://eslint.org/docs/latest/rules/no-unused-private-class-members" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -38,3 +38,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-unused-vars" | ||
url: "https://eslint.org/docs/latest/rules/no-unused-vars" | ||
}, | ||
@@ -88,3 +88,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -471,3 +471,4 @@ const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/u; | ||
parent.left === id && | ||
isUnusedExpression(parent) | ||
isUnusedExpression(parent) && | ||
!astUtils.isLogicalAssignmentOperator(parent.operator) | ||
) || | ||
@@ -561,3 +562,3 @@ ( | ||
const def = variable.defs[0]; | ||
const params = context.getDeclaredVariables(def.node); | ||
const params = sourceCode.getDeclaredVariables(def.node); | ||
const posteriorParams = params.slice(params.indexOf(variable) + 1); | ||
@@ -680,3 +681,3 @@ | ||
"Program:exit"(programNode) { | ||
const unusedVars = collectUnusedVariables(context.getScope(), []); | ||
const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []); | ||
@@ -683,0 +684,0 @@ for (let i = 0, l = unusedVars.length; i < l; ++i) { |
@@ -231,3 +231,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-use-before-define" | ||
url: "https://eslint.org/docs/latest/rules/no-use-before-define" | ||
}, | ||
@@ -262,2 +262,3 @@ | ||
const options = parseOptions(context.options[0]); | ||
const sourceCode = context.sourceCode; | ||
@@ -344,4 +345,4 @@ /** | ||
return { | ||
Program() { | ||
checkReferencesInScope(context.getScope()); | ||
Program(node) { | ||
checkReferencesInScope(sourceCode.getScope(node)); | ||
} | ||
@@ -348,0 +349,0 @@ }; |
@@ -12,4 +12,4 @@ /** | ||
const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); | ||
const { RegExpParser, visitRegExpAST } = require("regexpp"); | ||
const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); | ||
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); | ||
@@ -70,3 +70,3 @@ //------------------------------------------------------------------------------ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-useless-backreference" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-backreference" | ||
}, | ||
@@ -87,2 +87,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -99,3 +101,3 @@ * Checks and reports useless backreferences in the given regular expression. | ||
try { | ||
regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); | ||
regExpAST = parser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); | ||
} catch { | ||
@@ -174,4 +176,4 @@ | ||
}, | ||
Program() { | ||
const scope = context.getScope(), | ||
Program(node) { | ||
const scope = sourceCode.getScope(node), | ||
tracker = new ReferenceTracker(scope), | ||
@@ -185,4 +187,4 @@ traceMap = { | ||
for (const { node } of tracker.iterateGlobalReferences(traceMap)) { | ||
const [patternNode, flagsNode] = node.arguments, | ||
for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { | ||
const [patternNode, flagsNode] = refNode.arguments, | ||
pattern = getStringIfConstant(patternNode, scope), | ||
@@ -192,3 +194,3 @@ flags = getStringIfConstant(flagsNode, scope); | ||
if (typeof pattern === "string") { | ||
checkRegex(node, pattern, flags || ""); | ||
checkRegex(refNode, pattern, flags || ""); | ||
} | ||
@@ -195,0 +197,0 @@ } |
@@ -60,3 +60,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-useless-call" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-call" | ||
}, | ||
@@ -72,3 +72,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -75,0 +75,0 @@ return { |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-useless-catch" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-catch" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -96,3 +96,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-useless-computed-key" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-computed-key" | ||
}, | ||
@@ -117,3 +117,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers; | ||
@@ -120,0 +120,0 @@ |
@@ -75,3 +75,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-useless-concat" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-concat" | ||
}, | ||
@@ -87,3 +87,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -90,0 +90,0 @@ return { |
@@ -143,3 +143,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-useless-constructor" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-constructor" | ||
}, | ||
@@ -146,0 +146,0 @@ |
@@ -9,3 +9,8 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); | ||
/** | ||
* @typedef {import('@eslint-community/regexpp').AST.CharacterClass} CharacterClass | ||
* @typedef {import('@eslint-community/regexpp').AST.ExpressionCharacterClass} ExpressionCharacterClass | ||
*/ | ||
//------------------------------------------------------------------------------ | ||
@@ -32,52 +37,14 @@ // Rule Definition | ||
/** | ||
* Parses a regular expression into a list of characters with character class info. | ||
* @param {string} regExpText The raw text used to create the regular expression | ||
* @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class. | ||
* @example | ||
* | ||
* parseRegExp("a\\b[cd-]"); | ||
* | ||
* // returns: | ||
* [ | ||
* { text: "a", index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false }, | ||
* { text: "b", index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false }, | ||
* { text: "c", index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false }, | ||
* { text: "d", index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false }, | ||
* { text: "-", index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false } | ||
* ]; | ||
* | ||
/* | ||
* Set of characters that require escaping in character classes in `unicodeSets` mode. | ||
* ( ) [ ] { } / - \ | are ClassSetSyntaxCharacter | ||
*/ | ||
function parseRegExp(regExpText) { | ||
const charList = []; | ||
const REGEX_CLASSSET_CHARACTER_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("q/[{}|()-")); | ||
regExpText.split("").reduce((state, char, index) => { | ||
if (!state.escapeNextChar) { | ||
if (char === "\\") { | ||
return Object.assign(state, { escapeNextChar: true }); | ||
} | ||
if (char === "[" && !state.inCharClass) { | ||
return Object.assign(state, { inCharClass: true, startingCharClass: true }); | ||
} | ||
if (char === "]" && state.inCharClass) { | ||
if (charList.length && charList[charList.length - 1].inCharClass) { | ||
charList[charList.length - 1].endsCharClass = true; | ||
} | ||
return Object.assign(state, { inCharClass: false, startingCharClass: false }); | ||
} | ||
} | ||
charList.push({ | ||
text: char, | ||
index, | ||
escaped: state.escapeNextChar, | ||
inCharClass: state.inCharClass, | ||
startsCharClass: state.startingCharClass, | ||
endsCharClass: false | ||
}); | ||
return Object.assign(state, { escapeNextChar: false, startingCharClass: false }); | ||
}, { escapeNextChar: false, inCharClass: false, startingCharClass: false }); | ||
/* | ||
* A single character set of ClassSetReservedDoublePunctuator. | ||
* && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator | ||
*/ | ||
const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set("!#$%&*+,.:;<=>?@^`~"); | ||
return charList; | ||
} | ||
/** @type {import('../shared/types').Rule} */ | ||
@@ -91,3 +58,3 @@ module.exports = { | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-useless-escape" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-escape" | ||
}, | ||
@@ -100,2 +67,3 @@ | ||
removeEscape: "Remove the `\\`. This maintains the current functionality.", | ||
removeEscapeDoNotKeepSemantics: "Remove the `\\` if it was inserted by mistake.", | ||
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." | ||
@@ -108,3 +76,4 @@ }, | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const parser = new RegExpParser(); | ||
@@ -116,5 +85,6 @@ /** | ||
* @param {string} character The uselessly escaped character (not including the backslash) | ||
* @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off. | ||
* @returns {void} | ||
*/ | ||
function report(node, startOffset, character) { | ||
function report(node, startOffset, character, disableEscapeBackslashSuggest) { | ||
const rangeStart = node.range[0] + startOffset; | ||
@@ -134,3 +104,6 @@ const range = [rangeStart, rangeStart + 1]; | ||
{ | ||
messageId: "removeEscape", | ||
// Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality. | ||
messageId: astUtils.isDirective(node.parent) | ||
? "removeEscapeDoNotKeepSemantics" : "removeEscape", | ||
fix(fixer) { | ||
@@ -140,8 +113,12 @@ return fixer.removeRange(range); | ||
}, | ||
{ | ||
messageId: "escapeBackslash", | ||
fix(fixer) { | ||
return fixer.insertTextBeforeRange(range, "\\"); | ||
} | ||
} | ||
...disableEscapeBackslashSuggest | ||
? [] | ||
: [ | ||
{ | ||
messageId: "escapeBackslash", | ||
fix(fixer) { | ||
return fixer.insertTextBeforeRange(range, "\\"); | ||
} | ||
} | ||
] | ||
] | ||
@@ -190,2 +167,129 @@ }); | ||
/** | ||
* Checks if the escape character in given regexp is unnecessary. | ||
* @private | ||
* @param {ASTNode} node node to validate. | ||
* @returns {void} | ||
*/ | ||
function validateRegExp(node) { | ||
const { pattern, flags } = node.regex; | ||
let patternNode; | ||
const unicode = flags.includes("u"); | ||
const unicodeSets = flags.includes("v"); | ||
try { | ||
patternNode = parser.parsePattern(pattern, 0, pattern.length, { unicode, unicodeSets }); | ||
} catch { | ||
// Ignore regular expressions with syntax errors | ||
return; | ||
} | ||
/** @type {(CharacterClass | ExpressionCharacterClass)[]} */ | ||
const characterClassStack = []; | ||
visitRegExpAST(patternNode, { | ||
onCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), | ||
onCharacterClassLeave: () => characterClassStack.shift(), | ||
onExpressionCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), | ||
onExpressionCharacterClassLeave: () => characterClassStack.shift(), | ||
onCharacterEnter(characterNode) { | ||
if (!characterNode.raw.startsWith("\\")) { | ||
// It's not an escaped character. | ||
return; | ||
} | ||
const escapedChar = characterNode.raw.slice(1); | ||
if (escapedChar !== String.fromCodePoint(characterNode.value)) { | ||
// It's a valid escape. | ||
return; | ||
} | ||
let allowedEscapes; | ||
if (characterClassStack.length) { | ||
allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES; | ||
} else { | ||
allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES; | ||
} | ||
if (allowedEscapes.has(escapedChar)) { | ||
return; | ||
} | ||
const reportedIndex = characterNode.start + 1; | ||
let disableEscapeBackslashSuggest = false; | ||
if (characterClassStack.length) { | ||
const characterClassNode = characterClassStack[0]; | ||
if (escapedChar === "^") { | ||
/* | ||
* The '^' character is also a special case; it must always be escaped outside of character classes, but | ||
* it only needs to be escaped in character classes if it's at the beginning of the character class. To | ||
* account for this, consider it to be a valid escape character outside of character classes, and filter | ||
* out '^' characters that appear at the start of a character class. | ||
*/ | ||
if (characterClassNode.start + 1 === characterNode.start) { | ||
return; | ||
} | ||
} | ||
if (!unicodeSets) { | ||
if (escapedChar === "-") { | ||
/* | ||
* The '-' character is a special case, because it's only valid to escape it if it's in a character | ||
* class, and is not at either edge of the character class. To account for this, don't consider '-' | ||
* characters to be valid in general, and filter out '-' characters that appear in the middle of a | ||
* character class. | ||
*/ | ||
if (characterClassNode.start + 1 !== characterNode.start && characterNode.end !== characterClassNode.end - 1) { | ||
return; | ||
} | ||
} | ||
} else { // unicodeSets mode | ||
if (REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(escapedChar)) { | ||
// Escaping is valid if it is a ClassSetReservedDoublePunctuator. | ||
if (pattern[characterNode.end] === escapedChar) { | ||
return; | ||
} | ||
if (pattern[characterNode.start - 1] === escapedChar) { | ||
if (escapedChar !== "^") { | ||
return; | ||
} | ||
// If the previous character is a `negate` caret(`^`), escape to caret is unnecessary. | ||
if (!characterClassNode.negate) { | ||
return; | ||
} | ||
const negateCaretIndex = characterClassNode.start + 1; | ||
if (negateCaretIndex < characterNode.start - 1) { | ||
return; | ||
} | ||
} | ||
} | ||
if (characterNode.parent.type === "ClassIntersection" || characterNode.parent.type === "ClassSubtraction") { | ||
disableEscapeBackslashSuggest = true; | ||
} | ||
} | ||
} | ||
report( | ||
node, | ||
reportedIndex, | ||
escapedChar, | ||
disableEscapeBackslashSuggest | ||
); | ||
} | ||
}); | ||
} | ||
/** | ||
* Checks if a node has an escape. | ||
@@ -228,28 +332,3 @@ * @param {ASTNode} node node to check. | ||
} else if (node.regex) { | ||
parseRegExp(node.regex.pattern) | ||
/* | ||
* The '-' character is a special case, because it's only valid to escape it if it's in a character | ||
* class, and is not at either edge of the character class. To account for this, don't consider '-' | ||
* characters to be valid in general, and filter out '-' characters that appear in the middle of a | ||
* character class. | ||
*/ | ||
.filter(charInfo => !(charInfo.text === "-" && charInfo.inCharClass && !charInfo.startsCharClass && !charInfo.endsCharClass)) | ||
/* | ||
* The '^' character is also a special case; it must always be escaped outside of character classes, but | ||
* it only needs to be escaped in character classes if it's at the beginning of the character class. To | ||
* account for this, consider it to be a valid escape character outside of character classes, and filter | ||
* out '^' characters that appear at the start of a character class. | ||
*/ | ||
.filter(charInfo => !(charInfo.text === "^" && charInfo.startsCharClass)) | ||
// Filter out characters that aren't escaped. | ||
.filter(charInfo => charInfo.escaped) | ||
// Filter out characters that are valid to escape, based on their position in the regular expression. | ||
.filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text)) | ||
// Report all the remaining characters. | ||
.forEach(charInfo => report(node, charInfo.index, charInfo.text)); | ||
validateRegExp(node); | ||
} | ||
@@ -256,0 +335,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-useless-rename" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-rename" | ||
}, | ||
@@ -50,3 +50,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(), | ||
const sourceCode = context.sourceCode, | ||
options = context.options[0] || {}, | ||
@@ -53,0 +53,0 @@ ignoreDestructuring = options.ignoreDestructuring === true, |
@@ -60,2 +60,18 @@ /** | ||
/** | ||
* Checks all segments in a set and returns true if any are reachable. | ||
* @param {Set<CodePathSegment>} segments The segments to check. | ||
* @returns {boolean} True if any segment is reachable; false otherwise. | ||
*/ | ||
function isAnySegmentReachable(segments) { | ||
for (const segment of segments) { | ||
if (segment.reachable) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -73,3 +89,3 @@ // Rule Definition | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-useless-return" | ||
url: "https://eslint.org/docs/latest/rules/no-useless-return" | ||
}, | ||
@@ -87,4 +103,3 @@ | ||
const segmentInfoMap = new WeakMap(); | ||
const usedUnreachableSegments = new WeakSet(); | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let scopeInfo = null; | ||
@@ -158,5 +173,6 @@ | ||
* @param {CodePathSegment} segment The segment to get return statements. | ||
* @param {Set<CodePathSegment>} usedUnreachableSegments A set of segments that have already been traversed in this call. | ||
* @returns {void} | ||
*/ | ||
function markReturnStatementsOnSegmentAsUsed(segment) { | ||
function markReturnStatementsOnSegmentAsUsed(segment, usedUnreachableSegments) { | ||
if (!segment.reachable) { | ||
@@ -167,3 +183,3 @@ usedUnreachableSegments.add(segment); | ||
.filter(prevSegment => !usedUnreachableSegments.has(prevSegment)) | ||
.forEach(markReturnStatementsOnSegmentAsUsed); | ||
.forEach(prevSegment => markReturnStatementsOnSegmentAsUsed(prevSegment, usedUnreachableSegments)); | ||
return; | ||
@@ -174,6 +190,25 @@ } | ||
for (const node of info.uselessReturns) { | ||
info.uselessReturns = info.uselessReturns.filter(node => { | ||
if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) { | ||
const returnInitialRange = node.range[0]; | ||
const returnFinalRange = node.range[1]; | ||
const areBlocksInRange = scopeInfo.traversedTryBlockStatements.some(tryBlockStatement => { | ||
const blockInitialRange = tryBlockStatement.range[0]; | ||
const blockFinalRange = tryBlockStatement.range[1]; | ||
return ( | ||
returnInitialRange >= blockInitialRange && | ||
returnFinalRange <= blockFinalRange | ||
); | ||
}); | ||
if (areBlocksInRange) { | ||
return true; | ||
} | ||
} | ||
remove(scopeInfo.uselessReturns, node); | ||
} | ||
info.uselessReturns = []; | ||
return false; | ||
}); | ||
} | ||
@@ -195,5 +230,4 @@ | ||
scopeInfo | ||
.codePath | ||
.currentSegments | ||
.forEach(markReturnStatementsOnSegmentAsUsed); | ||
.forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set())); | ||
} | ||
@@ -207,3 +241,3 @@ | ||
// Makes and pushs a new scope information. | ||
// Makes and pushes a new scope information. | ||
onCodePathStart(codePath) { | ||
@@ -213,3 +247,5 @@ scopeInfo = { | ||
uselessReturns: [], | ||
codePath | ||
traversedTryBlockStatements: [], | ||
codePath, | ||
currentSegments: new Set() | ||
}; | ||
@@ -251,2 +287,5 @@ }, | ||
onCodePathSegmentStart(segment) { | ||
scopeInfo.currentSegments.add(segment); | ||
const info = { | ||
@@ -261,2 +300,14 @@ uselessReturns: getUselessReturns([], segment.allPrevSegments), | ||
onUnreachableCodePathSegmentStart(segment) { | ||
scopeInfo.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
scopeInfo.currentSegments.delete(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
scopeInfo.currentSegments.delete(segment); | ||
}, | ||
// Adds ReturnStatement node to check whether it's useless or not. | ||
@@ -273,3 +324,3 @@ ReturnStatement(node) { | ||
// Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647). | ||
!scopeInfo.codePath.currentSegments.some(s => s.reachable) | ||
!isAnySegmentReachable(scopeInfo.currentSegments) | ||
) { | ||
@@ -279,3 +330,3 @@ return; | ||
for (const segment of scopeInfo.codePath.currentSegments) { | ||
for (const segment of scopeInfo.currentSegments) { | ||
const info = segmentInfoMap.get(segment); | ||
@@ -291,2 +342,10 @@ | ||
"TryStatement > BlockStatement.block:exit"(node) { | ||
scopeInfo.traversedTryBlockStatements.push(node); | ||
}, | ||
"TryStatement:exit"() { | ||
scopeInfo.traversedTryBlockStatements.pop(); | ||
}, | ||
/* | ||
@@ -293,0 +352,0 @@ * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement. |
@@ -162,3 +162,3 @@ /** | ||
(defaultValue !== null && start >= defaultStart && end <= defaultEnd) || | ||
(start >= initStart && end <= initEnd) | ||
(!astUtils.isFunction(node) && start >= initStart && end <= initEnd) | ||
); | ||
@@ -191,3 +191,3 @@ }); | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-var" | ||
url: "https://eslint.org/docs/latest/rules/no-var" | ||
}, | ||
@@ -204,3 +204,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -216,3 +216,3 @@ /** | ||
} | ||
const variables = context.getDeclaredVariables(declarator); | ||
const variables = sourceCode.getDeclaredVariables(declarator); | ||
@@ -275,3 +275,3 @@ return variables.some(hasReferenceInTDZ(declarator.init)); | ||
function canFix(node) { | ||
const variables = context.getDeclaredVariables(node); | ||
const variables = sourceCode.getDeclaredVariables(node); | ||
const scopeNode = getScopeNode(node); | ||
@@ -278,0 +278,0 @@ |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-void" | ||
url: "https://eslint.org/docs/latest/rules/no-void" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-warning-comments" | ||
url: "https://eslint.org/docs/latest/rules/no-warning-comments" | ||
}, | ||
@@ -62,3 +62,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(), | ||
const sourceCode = context.sourceCode, | ||
configuration = context.options[0] || {}, | ||
@@ -65,0 +65,0 @@ warningTerms = configuration.terms || ["todo", "fixme", "xxx"], |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/no-whitespace-before-property" | ||
url: "https://eslint.org/docs/latest/rules/no-whitespace-before-property" | ||
}, | ||
@@ -38,3 +38,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -41,0 +41,0 @@ //-------------------------------------------------------------------------- |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/no-with" | ||
url: "https://eslint.org/docs/latest/rules/no-with" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/nonblock-statement-body-position" | ||
url: "https://eslint.org/docs/latest/rules/nonblock-statement-body-position" | ||
}, | ||
@@ -53,3 +53,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -56,0 +56,0 @@ //---------------------------------------------------------------------- |
@@ -155,3 +155,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/object-curly-newline" | ||
url: "https://eslint.org/docs/latest/rules/object-curly-newline" | ||
}, | ||
@@ -189,3 +189,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const normalizedOptions = normalizeOptions(context.options[0]); | ||
@@ -192,0 +192,0 @@ |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/object-curly-spacing" | ||
url: "https://eslint.org/docs/latest/rules/object-curly-spacing" | ||
}, | ||
@@ -55,3 +55,3 @@ | ||
const spaced = context.options[0] === "always", | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -86,3 +86,3 @@ /** | ||
function reportNoBeginningSpace(node, token) { | ||
const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true }); | ||
const nextToken = context.sourceCode.getTokenAfter(token, { includeComments: true }); | ||
@@ -109,3 +109,3 @@ context.report({ | ||
function reportNoEndingSpace(node, token) { | ||
const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true }); | ||
const previousToken = context.sourceCode.getTokenBefore(token, { includeComments: true }); | ||
@@ -112,0 +112,0 @@ context.report({ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/object-property-newline" | ||
url: "https://eslint.org/docs/latest/rules/object-property-newline" | ||
}, | ||
@@ -57,3 +57,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -60,0 +60,0 @@ return { |
@@ -33,3 +33,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/object-shorthand" | ||
url: "https://eslint.org/docs/latest/rules/object-shorthand" | ||
}, | ||
@@ -127,3 +127,3 @@ | ||
const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -359,7 +359,8 @@ //-------------------------------------------------------------------------- | ||
* Also, this marks all `arguments` identifiers so that they can be detected later. | ||
* @param {ASTNode} node The node representing the function. | ||
* @returns {void} | ||
*/ | ||
function enterFunction() { | ||
function enterFunction(node) { | ||
lexicalScopeStack.unshift(new Set()); | ||
context.getScope().variables.filter(variable => variable.name === "arguments").forEach(variable => { | ||
sourceCode.getScope(node).variables.filter(variable => variable.name === "arguments").forEach(variable => { | ||
variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier)); | ||
@@ -366,0 +367,0 @@ }); |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/one-var-declaration-per-line" | ||
url: "https://eslint.org/docs/latest/rules/one-var-declaration-per-line" | ||
}, | ||
@@ -22,0 +22,0 @@ |
@@ -39,3 +39,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/one-var" | ||
url: "https://eslint.org/docs/latest/rules/one-var" | ||
}, | ||
@@ -125,3 +125,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -128,0 +128,0 @@ //-------------------------------------------------------------------------- |
@@ -68,3 +68,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/operator-assignment" | ||
url: "https://eslint.org/docs/latest/rules/operator-assignment" | ||
}, | ||
@@ -87,3 +87,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -90,0 +90,0 @@ /** |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/operator-linebreak" | ||
url: "https://eslint.org/docs/latest/rules/operator-linebreak" | ||
}, | ||
@@ -73,3 +73,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -76,0 +76,0 @@ //-------------------------------------------------------------------------- |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/padded-blocks" | ||
url: "https://eslint.org/docs/latest/rules/padded-blocks" | ||
}, | ||
@@ -100,3 +100,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -103,0 +103,0 @@ /** |
@@ -134,45 +134,2 @@ /** | ||
/** | ||
* Check whether the given node is a directive or not. | ||
* @param {ASTNode} node The node to check. | ||
* @param {SourceCode} sourceCode The source code object to get tokens. | ||
* @returns {boolean} `true` if the node is a directive. | ||
*/ | ||
function isDirective(node, sourceCode) { | ||
return ( | ||
node.type === "ExpressionStatement" && | ||
( | ||
node.parent.type === "Program" || | ||
( | ||
node.parent.type === "BlockStatement" && | ||
astUtils.isFunction(node.parent.parent) | ||
) | ||
) && | ||
node.expression.type === "Literal" && | ||
typeof node.expression.value === "string" && | ||
!astUtils.isParenthesised(sourceCode, node.expression) | ||
); | ||
} | ||
/** | ||
* Check whether the given node is a part of directive prologue or not. | ||
* @param {ASTNode} node The node to check. | ||
* @param {SourceCode} sourceCode The source code object to get tokens. | ||
* @returns {boolean} `true` if the node is a part of directive prologue. | ||
*/ | ||
function isDirectivePrologue(node, sourceCode) { | ||
if (isDirective(node, sourceCode)) { | ||
for (const sibling of node.parent.body) { | ||
if (sibling === node) { | ||
break; | ||
} | ||
if (!isDirective(sibling, sourceCode)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
* Gets the actual last token. | ||
@@ -257,3 +214,3 @@ * | ||
const end = nextToken.range[0]; | ||
const text = context.getSourceCode().text | ||
const text = context.sourceCode.text | ||
.slice(start, end) | ||
@@ -289,3 +246,3 @@ .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); | ||
fix(fixer) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let prevToken = getActualLastToken(sourceCode, prevNode); | ||
@@ -372,8 +329,6 @@ const nextToken = sourceCode.getFirstTokenBetween( | ||
directive: { | ||
test: isDirectivePrologue | ||
test: astUtils.isDirective | ||
}, | ||
expression: { | ||
test: (node, sourceCode) => | ||
node.type === "ExpressionStatement" && | ||
!isDirectivePrologue(node, sourceCode) | ||
test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node) | ||
}, | ||
@@ -389,6 +344,6 @@ iife: { | ||
"multiline-expression": { | ||
test: (node, sourceCode) => | ||
test: node => | ||
node.loc.start.line !== node.loc.end.line && | ||
node.type === "ExpressionStatement" && | ||
!isDirectivePrologue(node, sourceCode) | ||
!astUtils.isDirective(node) | ||
}, | ||
@@ -441,3 +396,3 @@ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/padding-line-between-statements" | ||
url: "https://eslint.org/docs/latest/rules/padding-line-between-statements" | ||
}, | ||
@@ -484,3 +439,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const configureList = context.options || []; | ||
@@ -487,0 +442,0 @@ let scopeInfo = null; |
@@ -156,3 +156,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-arrow-callback" | ||
url: "https://eslint.org/docs/latest/rules/prefer-arrow-callback" | ||
}, | ||
@@ -189,3 +189,3 @@ | ||
const allowNamedFunctions = options.allowNamedFunctions; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -268,3 +268,3 @@ /* | ||
// Skip recursive functions. | ||
const nameVar = context.getDeclaredVariables(node)[0]; | ||
const nameVar = sourceCode.getDeclaredVariables(node)[0]; | ||
@@ -276,3 +276,3 @@ if (isFunctionName(nameVar) && nameVar.references.length > 0) { | ||
// Skip if it's using arguments. | ||
const variable = getVariableOfArguments(context.getScope()); | ||
const variable = getVariableOfArguments(sourceCode.getScope(node)); | ||
@@ -342,2 +342,3 @@ if (variable && variable.references.length > 0) { | ||
const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken); | ||
const tokenBeforeBody = sourceCode.getTokenBefore(node.body); | ||
@@ -356,3 +357,3 @@ if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) { | ||
} | ||
yield fixer.insertTextBefore(node.body, "=> "); | ||
yield fixer.insertTextAfter(tokenBeforeBody, " =>"); | ||
@@ -359,0 +360,0 @@ // Get the node that will become the new arrow function. |
@@ -337,3 +337,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-const" | ||
url: "https://eslint.org/docs/latest/rules/prefer-const" | ||
}, | ||
@@ -360,3 +360,3 @@ | ||
const options = context.options[0] || {}; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const shouldMatchAnyDestructuredVariable = options.destructuring !== "all"; | ||
@@ -498,3 +498,3 @@ const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true; | ||
if (node.kind === "let" && !isInitOfForStatement(node)) { | ||
variables.push(...context.getDeclaredVariables(node)); | ||
variables.push(...sourceCode.getDeclaredVariables(node)); | ||
} | ||
@@ -501,0 +501,0 @@ } |
@@ -31,3 +31,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-destructuring" | ||
url: "https://eslint.org/docs/latest/rules/prefer-destructuring" | ||
}, | ||
@@ -194,3 +194,3 @@ | ||
const rightNode = node.init; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -197,0 +197,0 @@ // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved. |
@@ -13,3 +13,3 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const { CALL, ReferenceTracker } = require("eslint-utils"); | ||
const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); | ||
@@ -59,2 +59,3 @@ //------------------------------------------------------------------------------ | ||
const parentPrecedence = astUtils.getPrecedence(parent); | ||
const needsParens = ( | ||
@@ -64,3 +65,3 @@ parent.type === "ClassDeclaration" || | ||
parent.type.endsWith("Expression") && | ||
astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR && | ||
(parentPrecedence === -1 || parentPrecedence >= PRECEDENCE_OF_EXPONENTIATION_EXPR) && | ||
!(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) && | ||
@@ -98,3 +99,3 @@ !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) && | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-exponentiation-operator" | ||
url: "https://eslint.org/docs/latest/rules/prefer-exponentiation-operator" | ||
}, | ||
@@ -111,3 +112,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -180,4 +181,4 @@ /** | ||
return { | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
const tracker = new ReferenceTracker(scope); | ||
@@ -190,4 +191,4 @@ const trackMap = { | ||
for (const { node } of tracker.iterateGlobalReferences(trackMap)) { | ||
report(node); | ||
for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { | ||
report(refNode); | ||
} | ||
@@ -194,0 +195,0 @@ } |
@@ -17,4 +17,4 @@ /** | ||
getStringIfConstant | ||
} = require("eslint-utils"); | ||
const regexpp = require("regexpp"); | ||
} = require("@eslint-community/eslint-utils"); | ||
const regexpp = require("@eslint-community/regexpp"); | ||
@@ -94,3 +94,3 @@ //------------------------------------------------------------------------------ | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-named-capture-group" | ||
url: "https://eslint.org/docs/latest/rules/prefer-named-capture-group" | ||
}, | ||
@@ -110,3 +110,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -118,10 +118,13 @@ /** | ||
* @param {ASTNode} regexNode AST node which contains the regular expression. | ||
* @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not. | ||
* @param {string|null} flags The regular expression flags to be checked. | ||
* @returns {void} | ||
*/ | ||
function checkRegex(pattern, node, regexNode, uFlag) { | ||
function checkRegex(pattern, node, regexNode, flags) { | ||
let ast; | ||
try { | ||
ast = parser.parsePattern(pattern, 0, pattern.length, uFlag); | ||
ast = parser.parsePattern(pattern, 0, pattern.length, { | ||
unicode: Boolean(flags && flags.includes("u")), | ||
unicodeSets: Boolean(flags && flags.includes("v")) | ||
}); | ||
} catch { | ||
@@ -155,7 +158,7 @@ | ||
if (node.regex) { | ||
checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u")); | ||
checkRegex(node.regex.pattern, node, node, node.regex.flags); | ||
} | ||
}, | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
const tracker = new ReferenceTracker(scope); | ||
@@ -169,8 +172,8 @@ const traceMap = { | ||
for (const { node } of tracker.iterateGlobalReferences(traceMap)) { | ||
const regex = getStringIfConstant(node.arguments[0]); | ||
const flags = getStringIfConstant(node.arguments[1]); | ||
for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { | ||
const regex = getStringIfConstant(refNode.arguments[0]); | ||
const flags = getStringIfConstant(refNode.arguments[1]); | ||
if (regex) { | ||
checkRegex(regex, node, node.arguments[0], flags && flags.includes("u")); | ||
checkRegex(regex, refNode, refNode.arguments[0], flags); | ||
} | ||
@@ -177,0 +180,0 @@ } |
@@ -50,3 +50,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-numeric-literals" | ||
url: "https://eslint.org/docs/latest/rules/prefer-numeric-literals" | ||
}, | ||
@@ -64,3 +64,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -67,0 +67,0 @@ //---------------------------------------------------------------------- |
@@ -55,3 +55,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-object-has-own" | ||
url: "https://eslint.org/docs/latest/rules/prefer-object-has-own" | ||
}, | ||
@@ -65,2 +65,5 @@ schema: [], | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
@@ -77,3 +80,3 @@ CallExpression(node) { | ||
// check `Object` scope | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
const variable = astUtils.getVariableByName(scope, "Object"); | ||
@@ -91,3 +94,2 @@ | ||
fix(fixer) { | ||
const sourceCode = context.getSourceCode(); | ||
@@ -94,0 +96,0 @@ if (sourceCode.getCommentsInside(node.callee).length > 0) { |
/** | ||
* @fileoverview Prefers object spread property over Object.assign | ||
* @author Sharmila Jesupaul | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
@@ -9,3 +8,3 @@ | ||
const { CALL, ReferenceTracker } = require("eslint-utils"); | ||
const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); | ||
const { | ||
@@ -253,3 +252,3 @@ isCommaToken, | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-object-spread" | ||
url: "https://eslint.org/docs/latest/rules/prefer-object-spread" | ||
}, | ||
@@ -267,7 +266,7 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
return { | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
const tracker = new ReferenceTracker(scope); | ||
@@ -281,18 +280,18 @@ const trackMap = { | ||
// Iterate all calls of `Object.assign` (only of the global variable `Object`). | ||
for (const { node } of tracker.iterateGlobalReferences(trackMap)) { | ||
for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { | ||
if ( | ||
node.arguments.length >= 1 && | ||
node.arguments[0].type === "ObjectExpression" && | ||
!hasArraySpread(node) && | ||
refNode.arguments.length >= 1 && | ||
refNode.arguments[0].type === "ObjectExpression" && | ||
!hasArraySpread(refNode) && | ||
!( | ||
node.arguments.length > 1 && | ||
hasArgumentsWithAccessors(node) | ||
refNode.arguments.length > 1 && | ||
hasArgumentsWithAccessors(refNode) | ||
) | ||
) { | ||
const messageId = node.arguments.length === 1 | ||
const messageId = refNode.arguments.length === 1 | ||
? "useLiteralMessage" | ||
: "useSpreadMessage"; | ||
const fix = defineFixer(node, sourceCode); | ||
const fix = defineFixer(refNode, sourceCode); | ||
context.report({ node, messageId, fix }); | ||
context.report({ node: refNode, messageId, fix }); | ||
} | ||
@@ -299,0 +298,0 @@ } |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-promise-reject-errors" | ||
url: "https://eslint.org/docs/latest/rules/prefer-promise-reject-errors" | ||
}, | ||
@@ -45,2 +45,3 @@ | ||
const ALLOW_EMPTY_REJECT = context.options.length && context.options[0].allowEmptyReject; | ||
const sourceCode = context.sourceCode; | ||
@@ -105,3 +106,3 @@ //---------------------------------------------------------------------- | ||
) { | ||
context.getDeclaredVariables(node.arguments[0]) | ||
sourceCode.getDeclaredVariables(node.arguments[0]) | ||
@@ -108,0 +109,0 @@ /* |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-reflect" | ||
url: "https://eslint.org/docs/latest/rules/prefer-reflect" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -13,5 +13,6 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils"); | ||
const { RegExpValidator, visitRegExpAST, RegExpParser } = require("regexpp"); | ||
const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); | ||
const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); | ||
const { canTokensBeAdjacent } = require("./utils/ast-utils"); | ||
const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions"); | ||
@@ -22,4 +23,2 @@ //------------------------------------------------------------------------------ | ||
const REGEXPP_LATEST_ECMA_VERSION = 2022; | ||
/** | ||
@@ -43,11 +42,2 @@ * Determines whether the given node is a string literal. | ||
/** | ||
* Determines whether the given node is a template literal without expressions. | ||
* @param {ASTNode} node Node to check. | ||
* @returns {boolean} True if the node is a template literal without expressions. | ||
*/ | ||
function isStaticTemplateLiteral(node) { | ||
return node.type === "TemplateLiteral" && node.expressions.length === 0; | ||
} | ||
const validPrecedingTokens = new Set([ | ||
@@ -131,3 +121,3 @@ "(", | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-regex-literals" | ||
url: "https://eslint.org/docs/latest/rules/prefer-regex-literals" | ||
}, | ||
@@ -153,2 +143,4 @@ | ||
replaceWithLiteral: "Replace with an equivalent regular expression literal.", | ||
replaceWithLiteralAndFlags: "Replace with an equivalent regular expression literal with flags '{{ flags }}'.", | ||
replaceWithIntendedLiteralAndFlags: "Replace with a regular expression literal with flags '{{ flags }}'.", | ||
unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.", | ||
@@ -161,3 +153,3 @@ unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor." | ||
const [{ disallowRedundantWrapping = false } = {}] = context.options; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -170,3 +162,3 @@ /** | ||
function isGlobalReference(node) { | ||
const scope = context.getScope(); | ||
const scope = sourceCode.getScope(node); | ||
const variable = findVariable(scope, node); | ||
@@ -187,3 +179,3 @@ | ||
isGlobalReference(astUtils.skipChainExpression(node.tag).object) && | ||
isStaticTemplateLiteral(node.quasi); | ||
astUtils.isStaticTemplateLiteral(node.quasi); | ||
} | ||
@@ -201,3 +193,3 @@ | ||
if (isStaticTemplateLiteral(node)) { | ||
if (astUtils.isStaticTemplateLiteral(node)) { | ||
return node.quasis[0].value.cooked; | ||
@@ -220,3 +212,3 @@ } | ||
return isStringLiteral(node) || | ||
isStaticTemplateLiteral(node) || | ||
astUtils.isStaticTemplateLiteral(node) || | ||
isStringRawTaggedStaticTemplateLiteral(node); | ||
@@ -262,3 +254,3 @@ } | ||
* @param {number} ecmaVersion The ecmaVersion to convert. | ||
* @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. | ||
* @returns {import("@eslint-community/regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. | ||
*/ | ||
@@ -272,2 +264,4 @@ function getRegexppEcmaVersion(ecmaVersion) { | ||
const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion); | ||
/** | ||
@@ -308,5 +302,85 @@ * Makes a character escaped or else returns null. | ||
/** | ||
* Checks whether the given regex and flags are valid for the ecma version or not. | ||
* @param {string} pattern The regex pattern to check. | ||
* @param {string | undefined} flags The regex flags to check. | ||
* @returns {boolean} True if the given regex pattern and flags are valid for the ecma version. | ||
*/ | ||
function isValidRegexForEcmaVersion(pattern, flags) { | ||
const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); | ||
try { | ||
validator.validatePattern(pattern, 0, pattern.length, { | ||
unicode: flags ? flags.includes("u") : false, | ||
unicodeSets: flags ? flags.includes("v") : false | ||
}); | ||
if (flags) { | ||
validator.validateFlags(flags); | ||
} | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Checks whether two given regex flags contain the same flags or not. | ||
* @param {string} flagsA The regex flags. | ||
* @param {string} flagsB The regex flags. | ||
* @returns {boolean} True if two regex flags contain same flags. | ||
*/ | ||
function areFlagsEqual(flagsA, flagsB) { | ||
return [...flagsA].sort().join("") === [...flagsB].sort().join(""); | ||
} | ||
/** | ||
* Merges two regex flags. | ||
* @param {string} flagsA The regex flags. | ||
* @param {string} flagsB The regex flags. | ||
* @returns {string} The merged regex flags. | ||
*/ | ||
function mergeRegexFlags(flagsA, flagsB) { | ||
const flagsSet = new Set([ | ||
...flagsA, | ||
...flagsB | ||
]); | ||
return [...flagsSet].join(""); | ||
} | ||
/** | ||
* Checks whether a give node can be fixed to the given regex pattern and flags. | ||
* @param {ASTNode} node The node to check. | ||
* @param {string} pattern The regex pattern to check. | ||
* @param {string} flags The regex flags | ||
* @returns {boolean} True if a node can be fixed to the given regex pattern and flags. | ||
*/ | ||
function canFixTo(node, pattern, flags) { | ||
const tokenBefore = sourceCode.getTokenBefore(node); | ||
return sourceCode.getCommentsInside(node).length === 0 && | ||
(!tokenBefore || validPrecedingTokens.has(tokenBefore.value)) && | ||
isValidRegexForEcmaVersion(pattern, flags); | ||
} | ||
/** | ||
* Returns a safe output code considering the before and after tokens. | ||
* @param {ASTNode} node The regex node. | ||
* @param {string} newRegExpValue The new regex expression value. | ||
* @returns {string} The output code. | ||
*/ | ||
function getSafeOutput(node, newRegExpValue) { | ||
const tokenBefore = sourceCode.getTokenBefore(node); | ||
const tokenAfter = sourceCode.getTokenAfter(node); | ||
return (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") + | ||
newRegExpValue + | ||
(tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : ""); | ||
} | ||
return { | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
const tracker = new ReferenceTracker(scope); | ||
@@ -320,36 +394,81 @@ const traceMap = { | ||
for (const { node } of tracker.iterateGlobalReferences(traceMap)) { | ||
if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) { | ||
if (node.arguments.length === 2) { | ||
context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" }); | ||
for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { | ||
if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(refNode)) { | ||
const regexNode = refNode.arguments[0]; | ||
if (refNode.arguments.length === 2) { | ||
const suggests = []; | ||
const argFlags = getStringValue(refNode.arguments[1]) || ""; | ||
if (canFixTo(refNode, regexNode.regex.pattern, argFlags)) { | ||
suggests.push({ | ||
messageId: "replaceWithLiteralAndFlags", | ||
pattern: regexNode.regex.pattern, | ||
flags: argFlags | ||
}); | ||
} | ||
const literalFlags = regexNode.regex.flags || ""; | ||
const mergedFlags = mergeRegexFlags(literalFlags, argFlags); | ||
if ( | ||
!areFlagsEqual(mergedFlags, argFlags) && | ||
canFixTo(refNode, regexNode.regex.pattern, mergedFlags) | ||
) { | ||
suggests.push({ | ||
messageId: "replaceWithIntendedLiteralAndFlags", | ||
pattern: regexNode.regex.pattern, | ||
flags: mergedFlags | ||
}); | ||
} | ||
context.report({ | ||
node: refNode, | ||
messageId: "unexpectedRedundantRegExpWithFlags", | ||
suggest: suggests.map(({ flags, pattern, messageId }) => ({ | ||
messageId, | ||
data: { | ||
flags | ||
}, | ||
fix(fixer) { | ||
return fixer.replaceText(refNode, getSafeOutput(refNode, `/${pattern}/${flags}`)); | ||
} | ||
})) | ||
}); | ||
} else { | ||
context.report({ node, messageId: "unexpectedRedundantRegExp" }); | ||
const outputs = []; | ||
if (canFixTo(refNode, regexNode.regex.pattern, regexNode.regex.flags)) { | ||
outputs.push(sourceCode.getText(regexNode)); | ||
} | ||
context.report({ | ||
node: refNode, | ||
messageId: "unexpectedRedundantRegExp", | ||
suggest: outputs.map(output => ({ | ||
messageId: "replaceWithLiteral", | ||
fix(fixer) { | ||
return fixer.replaceText( | ||
refNode, | ||
getSafeOutput(refNode, output) | ||
); | ||
} | ||
})) | ||
}); | ||
} | ||
} else if (hasOnlyStaticStringArguments(node)) { | ||
let regexContent = getStringValue(node.arguments[0]); | ||
} else if (hasOnlyStaticStringArguments(refNode)) { | ||
let regexContent = getStringValue(refNode.arguments[0]); | ||
let noFix = false; | ||
let flags; | ||
if (node.arguments[1]) { | ||
flags = getStringValue(node.arguments[1]); | ||
if (refNode.arguments[1]) { | ||
flags = getStringValue(refNode.arguments[1]); | ||
} | ||
const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion); | ||
const RegExpValidatorInstance = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); | ||
try { | ||
RegExpValidatorInstance.validatePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false); | ||
if (flags) { | ||
RegExpValidatorInstance.validateFlags(flags); | ||
} | ||
} catch { | ||
if (!canFixTo(refNode, regexContent, flags)) { | ||
noFix = true; | ||
} | ||
const tokenBefore = sourceCode.getTokenBefore(node); | ||
if (tokenBefore && !validPrecedingTokens.has(tokenBefore.value)) { | ||
noFix = true; | ||
} | ||
if (!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.><?,'"|:;]*$/u.test(regexContent)) { | ||
@@ -359,10 +478,9 @@ noFix = true; | ||
if (sourceCode.getCommentsInside(node).length > 0) { | ||
noFix = true; | ||
} | ||
if (regexContent && !noFix) { | ||
let charIncrease = 0; | ||
const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false); | ||
const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, { | ||
unicode: flags ? flags.includes("u") : false, | ||
unicodeSets: flags ? flags.includes("v") : false | ||
}); | ||
@@ -390,3 +508,3 @@ visitRegExpAST(ast, { | ||
context.report({ | ||
node, | ||
node: refNode, | ||
messageId: "unexpectedRegExp", | ||
@@ -396,10 +514,3 @@ suggest: noFix ? [] : [{ | ||
fix(fixer) { | ||
const tokenAfter = sourceCode.getTokenAfter(node); | ||
return fixer.replaceText( | ||
node, | ||
(tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") + | ||
newRegExpValue + | ||
(tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : "") | ||
); | ||
return fixer.replaceText(refNode, getSafeOutput(refNode, newRegExpValue)); | ||
} | ||
@@ -406,0 +517,0 @@ }] |
@@ -70,3 +70,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-rest-params" | ||
url: "https://eslint.org/docs/latest/rules/prefer-rest-params" | ||
}, | ||
@@ -83,2 +83,4 @@ | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -99,6 +101,7 @@ * Reports a given reference. | ||
* Reports references of the implicit `arguments` variable if exist. | ||
* @param {ASTNode} node The node representing the function. | ||
* @returns {void} | ||
*/ | ||
function checkForArguments() { | ||
const argumentsVar = getVariableOfArguments(context.getScope()); | ||
function checkForArguments(node) { | ||
const argumentsVar = getVariableOfArguments(sourceCode.getScope(node)); | ||
@@ -105,0 +108,0 @@ if (argumentsVar) { |
@@ -54,3 +54,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-spread" | ||
url: "https://eslint.org/docs/latest/rules/prefer-spread" | ||
}, | ||
@@ -67,3 +67,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -70,0 +70,0 @@ return { |
@@ -133,3 +133,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/prefer-template" | ||
url: "https://eslint.org/docs/latest/rules/prefer-template" | ||
}, | ||
@@ -146,3 +146,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let done = Object.create(null); | ||
@@ -149,0 +149,0 @@ |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/quote-props" | ||
url: "https://eslint.org/docs/latest/rules/quote-props" | ||
}, | ||
@@ -90,3 +90,3 @@ | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -93,0 +93,0 @@ |
@@ -85,3 +85,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/quotes" | ||
url: "https://eslint.org/docs/latest/rules/quotes" | ||
}, | ||
@@ -127,3 +127,3 @@ | ||
allowTemplateLiterals = options && options.allowTemplateLiterals === true, | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
let avoidEscape = options && options.avoidEscape === true; | ||
@@ -162,3 +162,4 @@ | ||
* Checks whether or not a given node is a directive. | ||
* The directive is a `ExpressionStatement` which has only a string literal. | ||
* The directive is a `ExpressionStatement` which has only a string literal not surrounded by | ||
* parentheses. | ||
* @param {ASTNode} node A node to check. | ||
@@ -172,3 +173,4 @@ * @returns {boolean} Whether or not the node is a directive. | ||
node.expression.type === "Literal" && | ||
typeof node.expression.value === "string" | ||
typeof node.expression.value === "string" && | ||
!astUtils.isParenthesised(sourceCode, node.expression) | ||
); | ||
@@ -178,14 +180,13 @@ } | ||
/** | ||
* Checks whether or not a given node is a part of directive prologues. | ||
* See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive | ||
* Checks whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. | ||
* @see {@link http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive} | ||
* @param {ASTNode} node A node to check. | ||
* @returns {boolean} Whether or not the node is a part of directive prologues. | ||
* @returns {boolean} Whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. | ||
* @private | ||
*/ | ||
function isPartOfDirectivePrologue(node) { | ||
const block = node.parent.parent; | ||
if (block.type !== "Program" && (block.type !== "BlockStatement" || !astUtils.isFunction(block.parent))) { | ||
function isExpressionInOrJustAfterDirectivePrologue(node) { | ||
if (!astUtils.isTopLevelExpressionStatement(node.parent)) { | ||
return false; | ||
} | ||
const block = node.parent.parent; | ||
@@ -220,3 +221,3 @@ // Check the node is at a prologue. | ||
case "ExpressionStatement": | ||
return isPartOfDirectivePrologue(node); | ||
return !astUtils.isParenthesised(sourceCode, node) && isExpressionInOrJustAfterDirectivePrologue(node); | ||
@@ -337,8 +338,7 @@ // LiteralPropertyName. | ||
fix(fixer) { | ||
if (isPartOfDirectivePrologue(node)) { | ||
if (astUtils.isTopLevelExpressionStatement(node.parent) && !astUtils.isParenthesised(sourceCode, node)) { | ||
/* | ||
* TemplateLiterals in a directive prologue aren't actually directives, but if they're | ||
* in the directive prologue, then fixing them might turn them into directives and change | ||
* the behavior of the code. | ||
* TemplateLiterals aren't actually directives, but fixing them might turn | ||
* them into directives and change the behavior of the code. | ||
*/ | ||
@@ -345,0 +345,0 @@ return null; |
@@ -85,3 +85,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/radix" | ||
url: "https://eslint.org/docs/latest/rules/radix" | ||
}, | ||
@@ -108,2 +108,3 @@ | ||
const mode = context.options[0] || MODE_ALWAYS; | ||
const sourceCode = context.sourceCode; | ||
@@ -136,3 +137,2 @@ /** | ||
fix(fixer) { | ||
const sourceCode = context.getSourceCode(); | ||
const tokens = sourceCode.getTokens(node); | ||
@@ -168,4 +168,4 @@ const lastToken = tokens[tokens.length - 1]; // Parenthesis. | ||
return { | ||
"Program:exit"() { | ||
const scope = context.getScope(); | ||
"Program:exit"(node) { | ||
const scope = sourceCode.getScope(node); | ||
let variable; | ||
@@ -177,6 +177,6 @@ | ||
variable.references.forEach(reference => { | ||
const node = reference.identifier; | ||
const idNode = reference.identifier; | ||
if (astUtils.isCallee(node)) { | ||
checkArguments(node.parent); | ||
if (astUtils.isCallee(idNode)) { | ||
checkArguments(idNode.parent); | ||
} | ||
@@ -190,8 +190,8 @@ }); | ||
variable.references.forEach(reference => { | ||
const node = reference.identifier.parent; | ||
const maybeCallee = node.parent.type === "ChainExpression" | ||
? node.parent | ||
: node; | ||
const parentNode = reference.identifier.parent; | ||
const maybeCallee = parentNode.parent.type === "ChainExpression" | ||
? parentNode.parent | ||
: parentNode; | ||
if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) { | ||
if (isParseIntMethod(parentNode) && astUtils.isCallee(maybeCallee)) { | ||
checkArguments(maybeCallee.parent); | ||
@@ -198,0 +198,0 @@ } |
@@ -176,3 +176,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/require-atomic-updates" | ||
url: "https://eslint.org/docs/latest/rules/require-atomic-updates" | ||
}, | ||
@@ -202,3 +202,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const assignmentReferences = new Map(); | ||
@@ -209,4 +209,4 @@ const segmentInfo = new SegmentInfo(); | ||
return { | ||
onCodePathStart(codePath) { | ||
const scope = context.getScope(); | ||
onCodePathStart(codePath, node) { | ||
const scope = sourceCode.getScope(node); | ||
const shouldVerify = | ||
@@ -219,3 +219,4 @@ scope.type === "function" && | ||
codePath, | ||
referenceMap: shouldVerify ? createReferenceMap(scope) : null | ||
referenceMap: shouldVerify ? createReferenceMap(scope) : null, | ||
currentSegments: new Set() | ||
}; | ||
@@ -230,7 +231,21 @@ }, | ||
segmentInfo.initialize(segment); | ||
stack.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentStart(segment) { | ||
stack.currentSegments.add(segment); | ||
}, | ||
onUnreachableCodePathSegmentEnd(segment) { | ||
stack.currentSegments.delete(segment); | ||
}, | ||
onCodePathSegmentEnd(segment) { | ||
stack.currentSegments.delete(segment); | ||
}, | ||
// Handle references to prepare verification. | ||
Identifier(node) { | ||
const { codePath, referenceMap } = stack; | ||
const { referenceMap } = stack; | ||
const reference = referenceMap && referenceMap.get(node); | ||
@@ -248,3 +263,3 @@ | ||
if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) { | ||
segmentInfo.markAsRead(codePath.currentSegments, variable); | ||
segmentInfo.markAsRead(stack.currentSegments, variable); | ||
} | ||
@@ -276,6 +291,5 @@ | ||
":expression:exit"(node) { | ||
const { codePath, referenceMap } = stack; | ||
// referenceMap exists if this is in a resumable function scope. | ||
if (!referenceMap) { | ||
if (!stack.referenceMap) { | ||
return; | ||
@@ -286,3 +300,3 @@ } | ||
if (node.type === "AwaitExpression" || node.type === "YieldExpression") { | ||
segmentInfo.makeOutdated(codePath.currentSegments); | ||
segmentInfo.makeOutdated(stack.currentSegments); | ||
} | ||
@@ -299,3 +313,3 @@ | ||
if (segmentInfo.isOutdated(codePath.currentSegments, variable)) { | ||
if (segmentInfo.isOutdated(stack.currentSegments, variable)) { | ||
if (node.parent.left === reference.identifier) { | ||
@@ -302,0 +316,0 @@ context.report({ |
@@ -39,3 +39,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/require-await" | ||
url: "https://eslint.org/docs/latest/rules/require-await" | ||
}, | ||
@@ -51,3 +51,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
let scopeInfo = null; | ||
@@ -54,0 +54,0 @@ |
@@ -16,3 +16,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/require-jsdoc" | ||
url: "https://eslint.org/docs/latest/rules/require-jsdoc" | ||
}, | ||
@@ -65,3 +65,3 @@ | ||
create(context) { | ||
const source = context.getSourceCode(); | ||
const source = context.sourceCode; | ||
const DEFAULT_OPTIONS = { | ||
@@ -68,0 +68,0 @@ FunctionDeclaration: true, |
@@ -17,3 +17,5 @@ /** | ||
getStringIfConstant | ||
} = require("eslint-utils"); | ||
} = require("@eslint-community/eslint-utils"); | ||
const astUtils = require("./utils/ast-utils.js"); | ||
const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); | ||
@@ -30,8 +32,11 @@ //------------------------------------------------------------------------------ | ||
docs: { | ||
description: "Enforce the use of `u` flag on RegExp", | ||
description: "Enforce the use of `u` or `v` flag on RegExp", | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/require-unicode-regexp" | ||
url: "https://eslint.org/docs/latest/rules/require-unicode-regexp" | ||
}, | ||
hasSuggestions: true, | ||
messages: { | ||
addUFlag: "Add the 'u' flag.", | ||
requireUFlag: "Use the 'u' flag." | ||
@@ -44,2 +49,5 @@ }, | ||
create(context) { | ||
const sourceCode = context.sourceCode; | ||
return { | ||
@@ -49,9 +57,22 @@ "Literal[regex]"(node) { | ||
if (!flags.includes("u")) { | ||
context.report({ node, messageId: "requireUFlag" }); | ||
if (!flags.includes("u") && !flags.includes("v")) { | ||
context.report({ | ||
messageId: "requireUFlag", | ||
node, | ||
suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern) | ||
? [ | ||
{ | ||
fix(fixer) { | ||
return fixer.insertTextAfter(node, "u"); | ||
}, | ||
messageId: "addUFlag" | ||
} | ||
] | ||
: null | ||
}); | ||
} | ||
}, | ||
Program() { | ||
const scope = context.getScope(); | ||
Program(node) { | ||
const scope = sourceCode.getScope(node); | ||
const tracker = new ReferenceTracker(scope); | ||
@@ -62,8 +83,47 @@ const trackMap = { | ||
for (const { node } of tracker.iterateGlobalReferences(trackMap)) { | ||
const flagsNode = node.arguments[1]; | ||
for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { | ||
const [patternNode, flagsNode] = refNode.arguments; | ||
if (patternNode && patternNode.type === "SpreadElement") { | ||
continue; | ||
} | ||
const pattern = getStringIfConstant(patternNode, scope); | ||
const flags = getStringIfConstant(flagsNode, scope); | ||
if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { | ||
context.report({ node, messageId: "requireUFlag" }); | ||
if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) { | ||
context.report({ | ||
messageId: "requireUFlag", | ||
node: refNode, | ||
suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern) | ||
? [ | ||
{ | ||
fix(fixer) { | ||
if (flagsNode) { | ||
if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { | ||
const flagsNodeText = sourceCode.getText(flagsNode); | ||
return fixer.replaceText(flagsNode, [ | ||
flagsNodeText.slice(0, flagsNodeText.length - 1), | ||
flagsNodeText.slice(flagsNodeText.length - 1) | ||
].join("u")); | ||
} | ||
// We intentionally don't suggest concatenating + "u" to non-literals | ||
return null; | ||
} | ||
const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis | ||
return fixer.insertTextAfter( | ||
penultimateToken, | ||
astUtils.isCommaToken(penultimateToken) | ||
? ' "u",' | ||
: ', "u"' | ||
); | ||
}, | ||
messageId: "addUFlag" | ||
} | ||
] | ||
: null | ||
}); | ||
} | ||
@@ -70,0 +130,0 @@ } |
@@ -20,3 +20,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/require-yield" | ||
url: "https://eslint.org/docs/latest/rules/require-yield" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/rest-spread-spacing" | ||
url: "https://eslint.org/docs/latest/rules/rest-spread-spacing" | ||
}, | ||
@@ -39,3 +39,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(), | ||
const sourceCode = context.sourceCode, | ||
alwaysSpace = context.options[0] === "always"; | ||
@@ -42,0 +42,0 @@ |
@@ -22,3 +22,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/semi-spacing" | ||
url: "https://eslint.org/docs/latest/rules/semi-spacing" | ||
}, | ||
@@ -56,3 +56,3 @@ | ||
const config = context.options[0], | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
let requireSpaceBefore = false, | ||
@@ -59,0 +59,0 @@ requireSpaceAfter = true; |
@@ -78,3 +78,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/semi-style" | ||
url: "https://eslint.org/docs/latest/rules/semi-style" | ||
}, | ||
@@ -91,3 +91,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const option = context.options[0] || "last"; | ||
@@ -94,0 +94,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/semi" | ||
url: "https://eslint.org/docs/latest/rules/semi" | ||
}, | ||
@@ -62,3 +62,4 @@ | ||
properties: { | ||
omitLastInOneLineBlock: { type: "boolean" } | ||
omitLastInOneLineBlock: { type: "boolean" }, | ||
omitLastInOneLineClassBody: { type: "boolean" } | ||
}, | ||
@@ -88,4 +89,5 @@ additionalProperties: false | ||
const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock); | ||
const exceptOneLineClassBody = Boolean(options && options.omitLastInOneLineClassBody); | ||
const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any"; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -341,2 +343,23 @@ //-------------------------------------------------------------------------- | ||
/** | ||
* Checks a node to see if it's the last item in a one-liner `ClassBody` node. | ||
* ClassBody is a one-liner if its braces (and consequently everything between them) are on the same line. | ||
* @param {ASTNode} node The node to check. | ||
* @returns {boolean} whether the node is the last item in a one-liner ClassBody. | ||
*/ | ||
function isLastInOneLinerClassBody(node) { | ||
const parent = node.parent; | ||
const nextToken = sourceCode.getTokenAfter(node); | ||
if (!nextToken || nextToken.value !== "}") { | ||
return false; | ||
} | ||
if (parent.type === "ClassBody") { | ||
return parent.loc.start.line === parent.loc.end.line; | ||
} | ||
return false; | ||
} | ||
/** | ||
* Checks a node to see if it's followed by a semicolon. | ||
@@ -361,6 +384,8 @@ * @param {ASTNode} node The node to check. | ||
const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node)); | ||
const oneLinerClassBody = (exceptOneLineClassBody && isLastInOneLinerClassBody(node)); | ||
const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody; | ||
if (isSemi && oneLinerBlock) { | ||
if (isSemi && oneLinerBlockOrClassBody) { | ||
report(node, true); | ||
} else if (!isSemi && !oneLinerBlock) { | ||
} else if (!isSemi && !oneLinerBlockOrClassBody) { | ||
report(node); | ||
@@ -367,0 +392,0 @@ } |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/sort-imports" | ||
url: "https://eslint.org/docs/latest/rules/sort-imports" | ||
}, | ||
@@ -75,3 +75,3 @@ | ||
allowSeparatedGroups = configuration.allowSeparatedGroups || false, | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
let previousDeclaration = null; | ||
@@ -78,0 +78,0 @@ |
@@ -86,3 +86,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/sort-keys" | ||
url: "https://eslint.org/docs/latest/rules/sort-keys" | ||
}, | ||
@@ -139,3 +139,3 @@ | ||
let stack = null; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -142,0 +142,0 @@ return { |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/sort-vars" | ||
url: "https://eslint.org/docs/latest/rules/sort-vars" | ||
}, | ||
@@ -48,3 +48,3 @@ | ||
ignoreCase = configuration.ignoreCase || false, | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
@@ -51,0 +51,0 @@ return { |
@@ -45,3 +45,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/space-before-blocks" | ||
url: "https://eslint.org/docs/latest/rules/space-before-blocks" | ||
}, | ||
@@ -84,3 +84,3 @@ | ||
const config = context.options[0], | ||
sourceCode = context.getSourceCode(); | ||
sourceCode = context.sourceCode; | ||
let alwaysFunctions = true, | ||
@@ -87,0 +87,0 @@ alwaysKeywords = true, |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/space-before-function-paren" | ||
url: "https://eslint.org/docs/latest/rules/space-before-function-paren" | ||
}, | ||
@@ -63,3 +63,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const baseConfig = typeof context.options[0] === "string" ? context.options[0] : "always"; | ||
@@ -66,0 +66,0 @@ const overrideConfig = typeof context.options[0] === "object" ? context.options[0] : {}; |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/space-in-parens" | ||
url: "https://eslint.org/docs/latest/rules/space-in-parens" | ||
}, | ||
@@ -106,3 +106,3 @@ | ||
//-------------------------------------------------------------------------- | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -109,0 +109,0 @@ /** |
@@ -21,3 +21,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/space-infix-ops" | ||
url: "https://eslint.org/docs/latest/rules/space-infix-ops" | ||
}, | ||
@@ -47,3 +47,3 @@ | ||
const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -50,0 +50,0 @@ /** |
@@ -25,3 +25,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/space-unary-ops" | ||
url: "https://eslint.org/docs/latest/rules/space-unary-ops" | ||
}, | ||
@@ -66,3 +66,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -69,0 +69,0 @@ //-------------------------------------------------------------------------- |
@@ -157,3 +157,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/spaced-comment" | ||
url: "https://eslint.org/docs/latest/rules/spaced-comment" | ||
}, | ||
@@ -239,3 +239,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -242,0 +242,0 @@ // Unless the first option is never, require a space |
@@ -74,3 +74,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/strict" | ||
url: "https://eslint.org/docs/latest/rules/strict" | ||
}, | ||
@@ -77,0 +77,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/switch-colon-spacing" | ||
url: "https://eslint.org/docs/latest/rules/switch-colon-spacing" | ||
}, | ||
@@ -50,3 +50,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const options = context.options[0] || {}; | ||
@@ -53,0 +53,0 @@ const beforeSpacing = options.before === true; // false by default |
@@ -27,3 +27,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/symbol-description" | ||
url: "https://eslint.org/docs/latest/rules/symbol-description" | ||
}, | ||
@@ -39,2 +39,4 @@ fixable: null, | ||
const sourceCode = context.sourceCode; | ||
/** | ||
@@ -56,4 +58,4 @@ * Reports if node does not conform the rule in case rule is set to | ||
return { | ||
"Program:exit"() { | ||
const scope = context.getScope(); | ||
"Program:exit"(node) { | ||
const scope = sourceCode.getScope(node); | ||
const variable = astUtils.getVariableByName(scope, "Symbol"); | ||
@@ -63,6 +65,6 @@ | ||
variable.references.forEach(reference => { | ||
const node = reference.identifier; | ||
const idNode = reference.identifier; | ||
if (astUtils.isCallee(node)) { | ||
checkArgument(node.parent); | ||
if (astUtils.isCallee(idNode)) { | ||
checkArgument(idNode.parent); | ||
} | ||
@@ -69,0 +71,0 @@ }); |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/template-curly-spacing" | ||
url: "https://eslint.org/docs/latest/rules/template-curly-spacing" | ||
}, | ||
@@ -44,3 +44,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
const always = context.options[0] === "always"; | ||
@@ -47,0 +47,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/template-tag-spacing" | ||
url: "https://eslint.org/docs/latest/rules/template-tag-spacing" | ||
}, | ||
@@ -37,3 +37,3 @@ | ||
const never = context.options[0] !== "always"; | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -40,0 +40,0 @@ /** |
@@ -19,3 +19,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/unicode-bom" | ||
url: "https://eslint.org/docs/latest/rules/unicode-bom" | ||
}, | ||
@@ -46,3 +46,3 @@ | ||
const sourceCode = context.getSourceCode(), | ||
const sourceCode = context.sourceCode, | ||
location = { column: 0, line: 1 }, | ||
@@ -49,0 +49,0 @@ requireBOM = context.options[0] || "never"; |
@@ -42,3 +42,3 @@ /** | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/use-isnan" | ||
url: "https://eslint.org/docs/latest/rules/use-isnan" | ||
}, | ||
@@ -45,0 +45,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/valid-jsdoc" | ||
url: "https://eslint.org/docs/latest/rules/valid-jsdoc" | ||
}, | ||
@@ -100,3 +100,3 @@ | ||
prefer = options.prefer || {}, | ||
sourceCode = context.getSourceCode(), | ||
sourceCode = context.sourceCode, | ||
@@ -103,0 +103,0 @@ // these both default to true, so you have to explicitly make them false |
@@ -8,2 +8,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const astUtils = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -20,3 +26,3 @@ //------------------------------------------------------------------------------ | ||
recommended: true, | ||
url: "https://eslint.org/docs/rules/valid-typeof" | ||
url: "https://eslint.org/docs/latest/rules/valid-typeof" | ||
}, | ||
@@ -49,3 +55,3 @@ | ||
OPERATORS = new Set(["==", "===", "!=", "!=="]); | ||
const sourceCode = context.sourceCode; | ||
const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals; | ||
@@ -83,4 +89,4 @@ | ||
Program() { | ||
globalScope = context.getScope(); | ||
Program(node) { | ||
globalScope = sourceCode.getScope(node); | ||
}, | ||
@@ -90,3 +96,3 @@ | ||
if (isTypeofExpression(node)) { | ||
const parent = context.getAncestors().pop(); | ||
const { parent } = node; | ||
@@ -96,3 +102,3 @@ if (parent.type === "BinaryExpression" && OPERATORS.has(parent.operator)) { | ||
if (sibling.type === "Literal" || sibling.type === "TemplateLiteral" && !sibling.expressions.length) { | ||
if (sibling.type === "Literal" || astUtils.isStaticTemplateLiteral(sibling)) { | ||
const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked; | ||
@@ -99,0 +105,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/vars-on-top" | ||
url: "https://eslint.org/docs/latest/rules/vars-on-top" | ||
}, | ||
@@ -23,0 +23,0 @@ |
@@ -13,3 +13,3 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const eslintUtils = require("eslint-utils"); | ||
const eslintUtils = require("@eslint-community/eslint-utils"); | ||
@@ -49,3 +49,3 @@ //---------------------------------------------------------------------- | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/wrap-iife" | ||
url: "https://eslint.org/docs/latest/rules/wrap-iife" | ||
}, | ||
@@ -82,3 +82,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -85,0 +85,0 @@ /** |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/wrap-regex" | ||
url: "https://eslint.org/docs/latest/rules/wrap-regex" | ||
}, | ||
@@ -33,3 +33,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -45,6 +45,5 @@ return { | ||
const afterToken = sourceCode.getTokenAfter(node); | ||
const ancestors = context.getAncestors(); | ||
const grandparent = ancestors[ancestors.length - 1]; | ||
const { parent } = node; | ||
if (grandparent.type === "MemberExpression" && grandparent.object === node && | ||
if (parent.type === "MemberExpression" && parent.object === node && | ||
!(beforeToken && beforeToken.value === "(" && afterToken && afterToken.value === ")")) { | ||
@@ -51,0 +50,0 @@ context.report({ |
@@ -20,3 +20,3 @@ /** | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/yield-star-spacing" | ||
url: "https://eslint.org/docs/latest/rules/yield-star-spacing" | ||
}, | ||
@@ -52,3 +52,3 @@ | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -55,0 +55,0 @@ const mode = (function(option) { |
@@ -62,11 +62,2 @@ /** | ||
/** | ||
* Determines whether a node is a Template Literal which can be determined statically. | ||
* @param {ASTNode} node Node to test | ||
* @returns {boolean} True if the node is a Template Literal without expression. | ||
*/ | ||
function isStaticTemplateLiteral(node) { | ||
return node.type === "TemplateLiteral" && node.expressions.length === 0; | ||
} | ||
/** | ||
* Determines whether a non-Literal node should be treated as a single Literal node. | ||
@@ -77,3 +68,3 @@ * @param {ASTNode} node Node to test | ||
function looksLikeLiteral(node) { | ||
return isNegativeNumericLiteral(node) || isStaticTemplateLiteral(node); | ||
return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node); | ||
} | ||
@@ -105,3 +96,3 @@ | ||
if (isStaticTemplateLiteral(node)) { | ||
if (astUtils.isStaticTemplateLiteral(node)) { | ||
return { | ||
@@ -129,3 +120,3 @@ type: "Literal", | ||
recommended: false, | ||
url: "https://eslint.org/docs/rules/yoda" | ||
url: "https://eslint.org/docs/latest/rules/yoda" | ||
}, | ||
@@ -169,3 +160,3 @@ | ||
const sourceCode = context.getSourceCode(); | ||
const sourceCode = context.sourceCode; | ||
@@ -351,3 +342,3 @@ /** | ||
isComparisonOperator(node.operator) && | ||
!(exceptRange && isRangeTest(context.getAncestors().pop())) | ||
!(exceptRange && isRangeTest(node.parent)) | ||
) { | ||
@@ -354,0 +345,0 @@ context.report({ |
@@ -8,2 +8,22 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const Graphemer = require("graphemer").default; | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
// eslint-disable-next-line no-control-regex -- intentionally including control characters | ||
const ASCII_REGEX = /^[\u0000-\u007f]*$/u; | ||
/** @type {Graphemer | undefined} */ | ||
let splitter; | ||
//------------------------------------------------------------------------------ | ||
// Public Interface | ||
//------------------------------------------------------------------------------ | ||
/** | ||
@@ -21,4 +41,22 @@ * Converts the first letter of a string to uppercase. | ||
/** | ||
* Counts graphemes in a given string. | ||
* @param {string} value A string to count graphemes. | ||
* @returns {number} The number of graphemes in `value`. | ||
*/ | ||
function getGraphemeCount(value) { | ||
if (ASCII_REGEX.test(value)) { | ||
return value.length; | ||
} | ||
if (!splitter) { | ||
splitter = new Graphemer(); | ||
} | ||
return splitter.countGraphemes(value); | ||
} | ||
module.exports = { | ||
upperCaseFirst | ||
upperCaseFirst, | ||
getGraphemeCount | ||
}; |
@@ -24,3 +24,3 @@ /** | ||
* @property {EcmaFeatures} [ecmaFeatures] The optional features. | ||
* @property {3|5|6|7|8|9|10|11|12|13|14|2015|2016|2017|2018|2019|2020|2021|2022|2023} [ecmaVersion] The ECMAScript version (or revision number). | ||
* @property {3|5|6|7|8|9|10|11|12|13|14|15|2015|2016|2017|2018|2019|2020|2021|2022|2023|2024} [ecmaVersion] The ECMAScript version (or revision number). | ||
* @property {"script"|"module"} [sourceType] The source code type. | ||
@@ -100,6 +100,8 @@ * @property {boolean} [allowReserved] Allowing the use of reserved words as identifiers in ES3. | ||
* @property {number} [endLine] The 1-based line number of the end location. | ||
* @property {boolean} fatal If `true` then this is a fatal error. | ||
* @property {boolean} [fatal] If `true` then this is a fatal error. | ||
* @property {{range:[number,number], text:string}} [fix] Information for autofix. | ||
* @property {number|undefined} line The 1-based line number. | ||
* @property {string} message The error message. | ||
* @property {string} [messageId] The ID of the message in the rule's meta. | ||
* @property {(string|null)} nodeType Type of node | ||
* @property {string|null} ruleId The ID of the rule which makes this message. | ||
@@ -115,6 +117,8 @@ * @property {0|1|2} severity The severity of this message. | ||
* @property {number} [endLine] The 1-based line number of the end location. | ||
* @property {boolean} fatal If `true` then this is a fatal error. | ||
* @property {boolean} [fatal] If `true` then this is a fatal error. | ||
* @property {{range:[number,number], text:string}} [fix] Information for autofix. | ||
* @property {number|undefined} line The 1-based line number. | ||
* @property {string} message The error message. | ||
* @property {string} [messageId] The ID of the message in the rule's meta. | ||
* @property {(string|null)} nodeType Type of node | ||
* @property {string|null} ruleId The ID of the rule which makes this message. | ||
@@ -121,0 +125,0 @@ * @property {0|1|2} severity The severity of this message. |
@@ -12,11 +12,27 @@ /** | ||
const | ||
{ isCommentToken } = require("eslint-utils"), | ||
{ isCommentToken } = require("@eslint-community/eslint-utils"), | ||
TokenStore = require("./token-store"), | ||
astUtils = require("../shared/ast-utils"), | ||
Traverser = require("../shared/traverser"); | ||
Traverser = require("../shared/traverser"), | ||
globals = require("../../conf/globals"), | ||
{ | ||
directivesPattern | ||
} = require("../shared/directives"), | ||
/* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */ | ||
ConfigCommentParser = require("../linter/config-comment-parser"), | ||
eslintScope = require("eslint-scope"); | ||
//------------------------------------------------------------------------------ | ||
// Type Definitions | ||
//------------------------------------------------------------------------------ | ||
/** @typedef {import("eslint-scope").Variable} Variable */ | ||
//------------------------------------------------------------------------------ | ||
// Private | ||
//------------------------------------------------------------------------------ | ||
const commentParser = new ConfigCommentParser(); | ||
/** | ||
@@ -48,2 +64,25 @@ * Validates that the given AST has the required information. | ||
/** | ||
* Retrieves globals for the given ecmaVersion. | ||
* @param {number} ecmaVersion The version to retrieve globals for. | ||
* @returns {Object} The globals for the given ecmaVersion. | ||
*/ | ||
function getGlobalsForEcmaVersion(ecmaVersion) { | ||
switch (ecmaVersion) { | ||
case 3: | ||
return globals.es3; | ||
case 5: | ||
return globals.es5; | ||
default: | ||
if (ecmaVersion < 2015) { | ||
return globals[`es${ecmaVersion + 2009}`]; | ||
} | ||
return globals[`es${ecmaVersion}`]; | ||
} | ||
} | ||
/** | ||
* Check to see if its a ES6 export declaration. | ||
@@ -83,2 +122,32 @@ * @param {ASTNode} astNode An AST node. | ||
/** | ||
* Normalizes a value for a global in a config | ||
* @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in | ||
* a global directive comment | ||
* @returns {("readable"|"writeable"|"off")} The value normalized as a string | ||
* @throws Error if global value is invalid | ||
*/ | ||
function normalizeConfigGlobal(configuredValue) { | ||
switch (configuredValue) { | ||
case "off": | ||
return "off"; | ||
case true: | ||
case "true": | ||
case "writeable": | ||
case "writable": | ||
return "writable"; | ||
case null: | ||
case false: | ||
case "false": | ||
case "readable": | ||
case "readonly": | ||
return "readonly"; | ||
default: | ||
throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`); | ||
} | ||
} | ||
/** | ||
* Determines if two nodes or tokens overlap. | ||
@@ -145,2 +214,112 @@ * @param {ASTNode|Token} first The first node or token to check. | ||
//----------------------------------------------------------------------------- | ||
// Directive Comments | ||
//----------------------------------------------------------------------------- | ||
/** | ||
* Extract the directive and the justification from a given directive comment and trim them. | ||
* @param {string} value The comment text to extract. | ||
* @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification. | ||
*/ | ||
function extractDirectiveComment(value) { | ||
const match = /\s-{2,}\s/u.exec(value); | ||
if (!match) { | ||
return { directivePart: value.trim(), justificationPart: "" }; | ||
} | ||
const directive = value.slice(0, match.index).trim(); | ||
const justification = value.slice(match.index + match[0].length).trim(); | ||
return { directivePart: directive, justificationPart: justification }; | ||
} | ||
/** | ||
* Ensures that variables representing built-in properties of the Global Object, | ||
* and any globals declared by special block comments, are present in the global | ||
* scope. | ||
* @param {Scope} globalScope The global scope. | ||
* @param {Object|undefined} configGlobals The globals declared in configuration | ||
* @param {Object|undefined} inlineGlobals The globals declared in the source code | ||
* @returns {void} | ||
*/ | ||
function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) { | ||
// Define configured global variables. | ||
for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(inlineGlobals)])) { | ||
/* | ||
* `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would | ||
* typically be caught when validating a config anyway (validity for inline global comments is checked separately). | ||
*/ | ||
const configValue = configGlobals[id] === void 0 ? void 0 : normalizeConfigGlobal(configGlobals[id]); | ||
const commentValue = inlineGlobals[id] && inlineGlobals[id].value; | ||
const value = commentValue || configValue; | ||
const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments; | ||
if (value === "off") { | ||
continue; | ||
} | ||
let variable = globalScope.set.get(id); | ||
if (!variable) { | ||
variable = new eslintScope.Variable(id, globalScope); | ||
globalScope.variables.push(variable); | ||
globalScope.set.set(id, variable); | ||
} | ||
variable.eslintImplicitGlobalSetting = configValue; | ||
variable.eslintExplicitGlobal = sourceComments !== void 0; | ||
variable.eslintExplicitGlobalComments = sourceComments; | ||
variable.writeable = (value === "writable"); | ||
} | ||
/* | ||
* "through" contains all references which definitions cannot be found. | ||
* Since we augment the global scope using configuration, we need to update | ||
* references and remove the ones that were added by configuration. | ||
*/ | ||
globalScope.through = globalScope.through.filter(reference => { | ||
const name = reference.identifier.name; | ||
const variable = globalScope.set.get(name); | ||
if (variable) { | ||
/* | ||
* Links the variable and the reference. | ||
* And this reference is removed from `Scope#through`. | ||
*/ | ||
reference.resolved = variable; | ||
variable.references.push(reference); | ||
return false; | ||
} | ||
return true; | ||
}); | ||
} | ||
/** | ||
* Sets the given variable names as exported so they won't be triggered by | ||
* the `no-unused-vars` rule. | ||
* @param {eslint.Scope} globalScope The global scope to define exports in. | ||
* @param {Record<string,string>} variables An object whose keys are the variable | ||
* names to export. | ||
* @returns {void} | ||
*/ | ||
function markExportedVariables(globalScope, variables) { | ||
Object.keys(variables).forEach(name => { | ||
const variable = globalScope.set.get(name); | ||
if (variable) { | ||
variable.eslintUsed = true; | ||
variable.eslintExported = true; | ||
} | ||
}); | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -150,2 +329,4 @@ // Public Interface | ||
const caches = Symbol("caches"); | ||
/** | ||
@@ -184,2 +365,11 @@ * Represents parsed source code. | ||
/** | ||
* General purpose caching for the class. | ||
*/ | ||
this[caches] = new Map([ | ||
["scopes", new WeakMap()], | ||
["vars", new Map()], | ||
["configNodes", void 0] | ||
]); | ||
/** | ||
* The flag to indicate that the source code has Unicode BOM. | ||
@@ -260,3 +450,3 @@ * @type {boolean} | ||
// don't allow modification of this object | ||
// don't allow further modification of this object | ||
Object.freeze(this); | ||
@@ -598,4 +788,297 @@ Object.freeze(this.lines); | ||
} | ||
/** | ||
* Gets the scope for the given node | ||
* @param {ASTNode} currentNode The node to get the scope of | ||
* @returns {eslint-scope.Scope} The scope information for this node | ||
* @throws {TypeError} If the `currentNode` argument is missing. | ||
*/ | ||
getScope(currentNode) { | ||
if (!currentNode) { | ||
throw new TypeError("Missing required argument: node."); | ||
} | ||
// check cache first | ||
const cache = this[caches].get("scopes"); | ||
const cachedScope = cache.get(currentNode); | ||
if (cachedScope) { | ||
return cachedScope; | ||
} | ||
// On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. | ||
const inner = currentNode.type !== "Program"; | ||
for (let node = currentNode; node; node = node.parent) { | ||
const scope = this.scopeManager.acquire(node, inner); | ||
if (scope) { | ||
if (scope.type === "function-expression-name") { | ||
cache.set(currentNode, scope.childScopes[0]); | ||
return scope.childScopes[0]; | ||
} | ||
cache.set(currentNode, scope); | ||
return scope; | ||
} | ||
} | ||
cache.set(currentNode, this.scopeManager.scopes[0]); | ||
return this.scopeManager.scopes[0]; | ||
} | ||
/** | ||
* Get the variables that `node` defines. | ||
* This is a convenience method that passes through | ||
* to the same method on the `scopeManager`. | ||
* @param {ASTNode} node The node for which the variables are obtained. | ||
* @returns {Array<Variable>} An array of variable nodes representing | ||
* the variables that `node` defines. | ||
*/ | ||
getDeclaredVariables(node) { | ||
return this.scopeManager.getDeclaredVariables(node); | ||
} | ||
/* eslint-disable class-methods-use-this -- node is owned by SourceCode */ | ||
/** | ||
* Gets all the ancestors of a given node | ||
* @param {ASTNode} node The node | ||
* @returns {Array<ASTNode>} All the ancestor nodes in the AST, not including the provided node, starting | ||
* from the root node at index 0 and going inwards to the parent node. | ||
* @throws {TypeError} When `node` is missing. | ||
*/ | ||
getAncestors(node) { | ||
if (!node) { | ||
throw new TypeError("Missing required argument: node."); | ||
} | ||
const ancestorsStartingAtParent = []; | ||
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { | ||
ancestorsStartingAtParent.push(ancestor); | ||
} | ||
return ancestorsStartingAtParent.reverse(); | ||
} | ||
/* eslint-enable class-methods-use-this -- node is owned by SourceCode */ | ||
/** | ||
* Marks a variable as used in the current scope | ||
* @param {string} name The name of the variable to mark as used. | ||
* @param {ASTNode} [refNode] The closest node to the variable reference. | ||
* @returns {boolean} True if the variable was found and marked as used, false if not. | ||
*/ | ||
markVariableAsUsed(name, refNode = this.ast) { | ||
const currentScope = this.getScope(refNode); | ||
let initialScope = currentScope; | ||
/* | ||
* When we are in an ESM or CommonJS module, we need to start searching | ||
* from the top-level scope, not the global scope. For ESM the top-level | ||
* scope is the module scope; for CommonJS the top-level scope is the | ||
* outer function scope. | ||
* | ||
* Without this check, we might miss a variable declared with `var` at | ||
* the top-level because it won't exist in the global scope. | ||
*/ | ||
if ( | ||
currentScope.type === "global" && | ||
currentScope.childScopes.length > 0 && | ||
// top-level scopes refer to a `Program` node | ||
currentScope.childScopes[0].block === this.ast | ||
) { | ||
initialScope = currentScope.childScopes[0]; | ||
} | ||
for (let scope = initialScope; scope; scope = scope.upper) { | ||
const variable = scope.variables.find(scopeVar => scopeVar.name === name); | ||
if (variable) { | ||
variable.eslintUsed = true; | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Returns an array of all inline configuration nodes found in the | ||
* source code. | ||
* @returns {Array<Token>} An array of all inline configuration nodes. | ||
*/ | ||
getInlineConfigNodes() { | ||
// check the cache first | ||
let configNodes = this[caches].get("configNodes"); | ||
if (configNodes) { | ||
return configNodes; | ||
} | ||
// calculate fresh config nodes | ||
configNodes = this.ast.comments.filter(comment => { | ||
// shebang comments are never directives | ||
if (comment.type === "Shebang") { | ||
return false; | ||
} | ||
const { directivePart } = extractDirectiveComment(comment.value); | ||
const directiveMatch = directivesPattern.exec(directivePart); | ||
if (!directiveMatch) { | ||
return false; | ||
} | ||
// only certain comment types are supported as line comments | ||
return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]); | ||
}); | ||
this[caches].set("configNodes", configNodes); | ||
return configNodes; | ||
} | ||
/** | ||
* Applies language options sent in from the core. | ||
* @param {Object} languageOptions The language options for this run. | ||
* @returns {void} | ||
*/ | ||
applyLanguageOptions(languageOptions) { | ||
/* | ||
* Add configured globals and language globals | ||
* | ||
* Using Object.assign instead of object spread for performance reasons | ||
* https://github.com/eslint/eslint/issues/16302 | ||
*/ | ||
const configGlobals = Object.assign( | ||
{}, | ||
getGlobalsForEcmaVersion(languageOptions.ecmaVersion), | ||
languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0, | ||
languageOptions.globals | ||
); | ||
const varsCache = this[caches].get("vars"); | ||
varsCache.set("configGlobals", configGlobals); | ||
} | ||
/** | ||
* Applies configuration found inside of the source code. This method is only | ||
* called when ESLint is running with inline configuration allowed. | ||
* @returns {{problems:Array<Problem>,configs:{config:FlatConfigArray,node:ASTNode}}} Information | ||
* that ESLint needs to further process the inline configuration. | ||
*/ | ||
applyInlineConfig() { | ||
const problems = []; | ||
const configs = []; | ||
const exportedVariables = {}; | ||
const inlineGlobals = Object.create(null); | ||
this.getInlineConfigNodes().forEach(comment => { | ||
const { directivePart } = extractDirectiveComment(comment.value); | ||
const match = directivesPattern.exec(directivePart); | ||
const directiveText = match[1]; | ||
const directiveValue = directivePart.slice(match.index + directiveText.length); | ||
switch (directiveText) { | ||
case "exported": | ||
Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment)); | ||
break; | ||
case "globals": | ||
case "global": | ||
for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) { | ||
let normalizedValue; | ||
try { | ||
normalizedValue = normalizeConfigGlobal(value); | ||
} catch (err) { | ||
problems.push({ | ||
ruleId: null, | ||
loc: comment.loc, | ||
message: err.message | ||
}); | ||
continue; | ||
} | ||
if (inlineGlobals[id]) { | ||
inlineGlobals[id].comments.push(comment); | ||
inlineGlobals[id].value = normalizedValue; | ||
} else { | ||
inlineGlobals[id] = { | ||
comments: [comment], | ||
value: normalizedValue | ||
}; | ||
} | ||
} | ||
break; | ||
case "eslint": { | ||
const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc); | ||
if (parseResult.success) { | ||
configs.push({ | ||
config: { | ||
rules: parseResult.config | ||
}, | ||
node: comment | ||
}); | ||
} else { | ||
problems.push(parseResult.error); | ||
} | ||
break; | ||
} | ||
// no default | ||
} | ||
}); | ||
// save all the new variables for later | ||
const varsCache = this[caches].get("vars"); | ||
varsCache.set("inlineGlobals", inlineGlobals); | ||
varsCache.set("exportedVariables", exportedVariables); | ||
return { | ||
configs, | ||
problems | ||
}; | ||
} | ||
/** | ||
* Called by ESLint core to indicate that it has finished providing | ||
* information. We now add in all the missing variables and ensure that | ||
* state-changing methods cannot be called by rules. | ||
* @returns {void} | ||
*/ | ||
finalize() { | ||
// Step 1: ensure that all of the necessary variables are up to date | ||
const varsCache = this[caches].get("vars"); | ||
const globalScope = this.scopeManager.scopes[0]; | ||
const configGlobals = varsCache.get("configGlobals"); | ||
const inlineGlobals = varsCache.get("inlineGlobals"); | ||
const exportedVariables = varsCache.get("exportedVariables"); | ||
addDeclaredGlobals(globalScope, configGlobals, inlineGlobals); | ||
if (exportedVariables) { | ||
markExportedVariables(globalScope, exportedVariables); | ||
} | ||
} | ||
} | ||
module.exports = SourceCode; |
@@ -12,3 +12,3 @@ /** | ||
const assert = require("assert"); | ||
const { isCommentToken } = require("eslint-utils"); | ||
const { isCommentToken } = require("@eslint-community/eslint-utils"); | ||
const cursors = require("./cursors"); | ||
@@ -15,0 +15,0 @@ const ForwardTokenCursor = require("./forward-token-cursor"); |
@@ -8,16 +8,2 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
/** | ||
* Gets `token.range[0]` from the given token. | ||
* @param {Node|Token|Comment} token The token to get. | ||
* @returns {number} The start location. | ||
* @private | ||
*/ | ||
function getStartLocation(token) { | ||
return token.range[0]; | ||
} | ||
//------------------------------------------------------------------------------ | ||
// Exports | ||
@@ -34,5 +20,24 @@ //------------------------------------------------------------------------------ | ||
exports.search = function search(tokens, location) { | ||
const index = tokens.findIndex(el => location <= getStartLocation(el)); | ||
for (let minIndex = 0, maxIndex = tokens.length - 1; minIndex <= maxIndex;) { | ||
return index === -1 ? tokens.length : index; | ||
/* | ||
* Calculate the index in the middle between minIndex and maxIndex. | ||
* `| 0` is used to round a fractional value down to the nearest integer: this is similar to | ||
* using `Math.trunc()` or `Math.floor()`, but performance tests have shown this method to | ||
* be faster. | ||
*/ | ||
const index = (minIndex + maxIndex) / 2 | 0; | ||
const token = tokens[index]; | ||
const tokenStartLocation = token.range[0]; | ||
if (location <= tokenStartLocation) { | ||
if (index === minIndex) { | ||
return index; | ||
} | ||
maxIndex = index; | ||
} else { | ||
minIndex = index + 1; | ||
} | ||
} | ||
return tokens.length; | ||
}; | ||
@@ -54,4 +59,9 @@ | ||
const index = indexMap[startLoc - 1]; | ||
const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; | ||
const token = tokens[index]; | ||
// If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array. | ||
if (!token) { | ||
return tokens.length; | ||
} | ||
/* | ||
@@ -61,3 +71,3 @@ * For the map of "comment's location -> token's index", it points the next token of a comment. | ||
*/ | ||
if (token && token.range[0] >= startLoc) { | ||
if (token.range[0] >= startLoc) { | ||
return index; | ||
@@ -84,4 +94,9 @@ } | ||
const index = indexMap[endLoc - 1]; | ||
const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; | ||
const token = tokens[index]; | ||
// If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array. | ||
if (!token) { | ||
return tokens.length - 1; | ||
} | ||
/* | ||
@@ -91,3 +106,3 @@ * For the map of "comment's location -> token's index", it points the next token of a comment. | ||
*/ | ||
if (token && token.range[1] > endLoc) { | ||
if (token.range[1] > endLoc) { | ||
return index - 1; | ||
@@ -94,0 +109,0 @@ } |
@@ -15,4 +15,5 @@ /** | ||
const { FileEnumerator } = require("./cli-engine/file-enumerator"); | ||
const { FlatESLint } = require("./eslint/flat-eslint"); | ||
const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"); | ||
const FlatRuleTester = require("./rule-tester/flat-rule-tester"); | ||
const { ESLint } = require("./eslint/eslint"); | ||
@@ -26,4 +27,6 @@ //----------------------------------------------------------------------------- | ||
FlatESLint, | ||
shouldUseFlatConfig, | ||
FlatRuleTester, | ||
FileEnumerator | ||
FileEnumerator, | ||
LegacyESLint: ESLint | ||
}; |
@@ -13,4 +13,4 @@ "use strict"; | ||
If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat/help | ||
If you think you already have a configuration file or if you need more help, please stop by the ESLint Discord server: https://eslint.org/chat | ||
`.trimStart(); | ||
}; |
@@ -6,4 +6,4 @@ "use strict"; | ||
The '--print-config' CLI option requires a path to a source code file rather than a directory. | ||
See also: https://eslint.org/docs/user-guide/command-line-interface#--print-config | ||
See also: https://eslint.org/docs/latest/use/command-line-interface#--print-config | ||
`.trimStart(); | ||
}; |
{ | ||
"name": "eslint", | ||
"version": "8.29.0", | ||
"version": "8.51.0", | ||
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>", | ||
@@ -16,18 +16,19 @@ "description": "An AST-based pattern checker for JavaScript.", | ||
"scripts": { | ||
"build:docs:update-links": "node tools/fetch-docs-links.js", | ||
"build:site": "node Makefile.js gensite", | ||
"build:webpack": "node Makefile.js webpack", | ||
"build:readme": "node tools/update-readme.js", | ||
"lint": "node Makefile.js lint", | ||
"lint:docs:js": "node Makefile.js lintDocsJS", | ||
"lint:fix": "node Makefile.js lint -- fix", | ||
"lint:fix:docs:js": "node Makefile.js lintDocsJS -- fix", | ||
"release:generate:alpha": "node Makefile.js generatePrerelease -- alpha", | ||
"release:generate:beta": "node Makefile.js generatePrerelease -- beta", | ||
"release:generate:latest": "node Makefile.js generateRelease", | ||
"release:generate:rc": "node Makefile.js generatePrerelease -- rc", | ||
"release:publish": "node Makefile.js publishRelease", | ||
"test": "node Makefile.js test", | ||
"test:cli": "mocha", | ||
"lint": "node Makefile.js lint", | ||
"lint:docsjs": "node Makefile.js lintDocsJS", | ||
"fix": "node Makefile.js lint -- fix", | ||
"fix:docsjs": "node Makefile.js lintDocsJS -- fix", | ||
"fuzz": "node Makefile.js fuzz", | ||
"generate-release": "node Makefile.js generateRelease", | ||
"generate-alpharelease": "node Makefile.js generatePrerelease -- alpha", | ||
"generate-betarelease": "node Makefile.js generatePrerelease -- beta", | ||
"generate-rcrelease": "node Makefile.js generatePrerelease -- rc", | ||
"publish-release": "node Makefile.js publishRelease", | ||
"gensite": "node Makefile.js gensite", | ||
"webpack": "node Makefile.js webpack", | ||
"perf": "node Makefile.js perf", | ||
"docs:update-links": "node tools/fetch-docs-links.js" | ||
"test:fuzz": "node Makefile.js fuzz", | ||
"test:performance": "node Makefile.js perf" | ||
}, | ||
@@ -40,2 +41,6 @@ "gitHooks": { | ||
"*.md": "markdownlint --fix", | ||
"lib/rules/*.js": [ | ||
"node tools/update-eslint-all.js", | ||
"git add packages/js/src/configs/eslint-all.js" | ||
], | ||
"docs/src/rules/*.md": [ | ||
@@ -60,7 +65,10 @@ "node tools/fetch-docs-links.js", | ||
"dependencies": { | ||
"@eslint/eslintrc": "^1.3.3", | ||
"@humanwhocodes/config-array": "^0.11.6", | ||
"@eslint-community/eslint-utils": "^4.2.0", | ||
"@eslint-community/regexpp": "^4.6.1", | ||
"@eslint/eslintrc": "^2.1.2", | ||
"@eslint/js": "8.51.0", | ||
"@humanwhocodes/config-array": "^0.11.11", | ||
"@humanwhocodes/module-importer": "^1.0.1", | ||
"@nodelib/fs.walk": "^1.2.8", | ||
"ajv": "^6.10.0", | ||
"ajv": "^6.12.4", | ||
"chalk": "^4.0.0", | ||
@@ -71,7 +79,6 @@ "cross-spawn": "^7.0.2", | ||
"escape-string-regexp": "^4.0.0", | ||
"eslint-scope": "^7.1.1", | ||
"eslint-utils": "^3.0.0", | ||
"eslint-visitor-keys": "^3.3.0", | ||
"espree": "^9.4.0", | ||
"esquery": "^1.4.0", | ||
"eslint-scope": "^7.2.2", | ||
"eslint-visitor-keys": "^3.4.3", | ||
"espree": "^9.6.1", | ||
"esquery": "^1.4.2", | ||
"esutils": "^2.0.2", | ||
@@ -82,10 +89,8 @@ "fast-deep-equal": "^3.1.3", | ||
"glob-parent": "^6.0.2", | ||
"globals": "^13.15.0", | ||
"grapheme-splitter": "^1.0.4", | ||
"globals": "^13.19.0", | ||
"graphemer": "^1.4.0", | ||
"ignore": "^5.2.0", | ||
"import-fresh": "^3.0.0", | ||
"imurmurhash": "^0.1.4", | ||
"is-glob": "^4.0.0", | ||
"is-path-inside": "^3.0.3", | ||
"js-sdsl": "^4.1.4", | ||
"js-yaml": "^4.1.0", | ||
@@ -97,6 +102,4 @@ "json-stable-stringify-without-jsonify": "^1.0.1", | ||
"natural-compare": "^1.4.0", | ||
"optionator": "^0.9.1", | ||
"regexpp": "^3.2.0", | ||
"optionator": "^0.9.3", | ||
"strip-ansi": "^6.0.1", | ||
"strip-json-comments": "^3.1.0", | ||
"text-table": "^0.2.0" | ||
@@ -107,2 +110,7 @@ }, | ||
"@babel/preset-env": "^7.4.3", | ||
"@wdio/browser-runner": "^8.14.6", | ||
"@wdio/cli": "^8.14.6", | ||
"@wdio/concise-reporter": "^8.14.0", | ||
"@wdio/globals": "^8.14.6", | ||
"@wdio/mocha-framework": "^8.14.0", | ||
"babel-loader": "^8.0.5", | ||
@@ -118,6 +126,6 @@ "c8": "^7.12.0", | ||
"eslint-plugin-eslint-comments": "^3.2.0", | ||
"eslint-plugin-eslint-plugin": "^4.4.0", | ||
"eslint-plugin-eslint-plugin": "^5.1.0", | ||
"eslint-plugin-internal-rules": "file:tools/internal-rules", | ||
"eslint-plugin-jsdoc": "^38.1.6", | ||
"eslint-plugin-n": "^15.2.4", | ||
"eslint-plugin-jsdoc": "^46.2.5", | ||
"eslint-plugin-n": "^16.0.0", | ||
"eslint-plugin-unicorn": "^42.0.0", | ||
@@ -132,7 +140,2 @@ "eslint-release": "^3.2.0", | ||
"gray-matter": "^4.0.3", | ||
"karma": "^6.1.1", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-mocha": "^2.0.1", | ||
"karma-mocha-reporter": "^2.2.5", | ||
"karma-webpack": "^5.0.0", | ||
"lint-staged": "^11.0.0", | ||
@@ -157,9 +160,10 @@ "load-perf": "^0.2.0", | ||
"proxyquire": "^2.0.1", | ||
"puppeteer": "^13.7.0", | ||
"recast": "^0.20.4", | ||
"regenerator-runtime": "^0.13.2", | ||
"semver": "^7.3.5", | ||
"rollup-plugin-node-polyfills": "^0.2.1", | ||
"semver": "^7.5.3", | ||
"shelljs": "^0.8.2", | ||
"sinon": "^11.0.0", | ||
"temp": "^0.9.0", | ||
"vite-plugin-commonjs": "^0.8.2", | ||
"webdriverio": "^8.14.6", | ||
"webpack": "^5.23.0", | ||
@@ -166,0 +170,0 @@ "webpack-cli": "^4.5.0", |
113
README.md
@@ -13,10 +13,10 @@ [![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint) | ||
[Website](https://eslint.org) | | ||
[Configuring](https://eslint.org/docs/user-guide/configuring) | | ||
[Configure ESLint](https://eslint.org/docs/latest/use/configure) | | ||
[Rules](https://eslint.org/docs/rules/) | | ||
[Contributing](https://eslint.org/docs/developer-guide/contributing) | | ||
[Reporting Bugs](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) | | ||
[Contribute to ESLint](https://eslint.org/docs/latest/contribute) | | ||
[Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) | | ||
[Code of Conduct](https://eslint.org/conduct) | | ||
[Twitter](https://twitter.com/geteslint) | | ||
[Mailing List](https://groups.google.com/group/eslint) | | ||
[Chat Room](https://eslint.org/chat) | ||
[Discord](https://eslint.org/chat) | | ||
[Mastodon](https://fosstodon.org/@eslint) | ||
@@ -35,3 +35,3 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: | ||
4. [Filing Issues](#filing-issues) | ||
5. [Frequently Asked Questions](#faq) | ||
5. [Frequently Asked Questions](#frequently-asked-questions) | ||
6. [Releases](#releases) | ||
@@ -46,3 +46,3 @@ 7. [Security Policy](#security-policy) | ||
## <a name="installation-and-usage"></a>Installation and Usage | ||
## Installation and Usage | ||
@@ -63,5 +63,5 @@ Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) | ||
## <a name="configuration"></a>Configuration | ||
## Configuration | ||
After running `npm init @eslint/config`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: | ||
After running `npm init @eslint/config`, you'll have an `.eslintrc` file in your directory. In it, you'll see some rules configured like this: | ||
@@ -83,18 +83,18 @@ ```json | ||
The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/user-guide/configuring)). | ||
The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/latest/use/configure)). | ||
## <a name="code-of-conduct"></a>Code of Conduct | ||
## Code of Conduct | ||
ESLint adheres to the [JS Foundation Code of Conduct](https://eslint.org/conduct). | ||
## <a name="filing-issues"></a>Filing Issues | ||
## Filing Issues | ||
Before filing an issue, please be sure to read the guidelines for what you're reporting: | ||
* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) | ||
* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules) | ||
* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes) | ||
* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes) | ||
* [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs) | ||
* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) | ||
* [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) | ||
* [Request a Change](https://eslint.org/docs/latest/contribute/request-change) | ||
## <a name="faq"></a>Frequently Asked Questions | ||
## Frequently Asked Questions | ||
@@ -105,3 +105,3 @@ ### I'm using JSCS, should I migrate to ESLint? | ||
We have prepared a [migration guide](https://eslint.org/docs/user-guide/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. | ||
We have prepared a [migration guide](https://eslint.org/docs/latest/use/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. | ||
@@ -122,7 +122,7 @@ We are now at or near 100% compatibility with JSCS. If you try ESLint and believe we are not yet compatible with a JSCS rule/configuration, please create an issue (mentioning that it is a JSCS compatibility issue) and we will evaluate it as per our normal process. | ||
Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/user-guide/configuring)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. | ||
Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/latest/use/configure)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. | ||
### What ECMAScript versions does ESLint support? | ||
ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021 and 2022. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring). | ||
ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, and 2023. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/latest/use/configure). | ||
@@ -135,7 +135,7 @@ ### What about experimental features? | ||
Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature. | ||
Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/latest/contribute). Until then, please use the appropriate parser and plugin(s) for your experimental feature. | ||
### Where to ask for help? | ||
Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://eslint.org/chat). | ||
Open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat). | ||
@@ -152,11 +152,11 @@ ### Why doesn't ESLint lock dependency versions? | ||
## <a name="releases"></a>Releases | ||
## Releases | ||
We have scheduled releases every two weeks on Friday or Saturday. You can follow a [release issue](https://github.com/eslint/eslint/issues?q=is%3Aopen+is%3Aissue+label%3Arelease) for updates about the scheduling of any particular release. | ||
## <a name="security-policy"></a>Security Policy | ||
## Security Policy | ||
ESLint takes security seriously. We work hard to ensure that ESLint is safe for everyone and that security issues are addressed quickly and responsibly. Read the full [security policy](https://github.com/eslint/.github/blob/master/SECURITY.md). | ||
## <a name="semantic-versioning-policy"></a>Semantic Versioning Policy | ||
## Semantic Versioning Policy | ||
@@ -194,3 +194,3 @@ ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint: | ||
## <a name="stylistic-rule-updates"></a>Stylistic Rule Updates | ||
## Stylistic Rule Updates | ||
@@ -204,7 +204,7 @@ Stylistic rules are frozen according to [our policy](https://eslint.org/blog/2020/05/changes-to-rules-policies) on how we evaluate new rules and rule changes. | ||
## <a name="license"></a>License | ||
## License | ||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large) | ||
## <a name="team"></a>Team | ||
## Team | ||
@@ -227,7 +227,2 @@ These folks keep the project moving and are resources for help. | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/btmills"> | ||
<img src="https://github.com/btmills.png?s=75" width="75" height="75"><br /> | ||
Brandon Mills | ||
</a> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/mdjermanovic"> | ||
@@ -260,7 +255,2 @@ <img src="https://github.com/mdjermanovic.png?s=75" width="75" height="75"><br /> | ||
<table><tbody><tr><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/brettz9"> | ||
<img src="https://github.com/brettz9.png?s=75" width="75" height="75"><br /> | ||
Brett Zamir | ||
</a> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/bmish"> | ||
@@ -271,20 +261,31 @@ <img src="https://github.com/bmish.png?s=75" width="75" height="75"><br /> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/SaraSoueidan"> | ||
<img src="https://github.com/SaraSoueidan.png?s=75" width="75" height="75"><br /> | ||
Sara Soueidan | ||
<a href="https://github.com/fasttime"> | ||
<img src="https://github.com/fasttime.png?s=75" width="75" height="75"><br /> | ||
Francesco Trotta | ||
</a> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/g-plane"> | ||
<img src="https://github.com/g-plane.png?s=75" width="75" height="75"><br /> | ||
Pig Fang | ||
<a href="https://github.com/ota-meshi"> | ||
<img src="https://github.com/ota-meshi.png?s=75" width="75" height="75"><br /> | ||
Yosuke Ota | ||
</a> | ||
</td></tr></tbody></table> | ||
### Website Team | ||
Team members who focus specifically on eslint.org | ||
<table><tbody><tr><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/amareshsm"> | ||
<img src="https://github.com/amareshsm.png?s=75" width="75" height="75"><br /> | ||
Amaresh S M | ||
</a> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/anikethsaha"> | ||
<img src="https://github.com/anikethsaha.png?s=75" width="75" height="75"><br /> | ||
Anix | ||
<a href="https://github.com/harish-sethuraman"> | ||
<img src="https://github.com/harish-sethuraman.png?s=75" width="75" height="75"><br /> | ||
Strek | ||
</a> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/yeonjuan"> | ||
<img src="https://github.com/yeonjuan.png?s=75" width="75" height="75"><br /> | ||
YeonJuan | ||
<a href="https://github.com/kecrily"> | ||
<img src="https://github.com/kecrily.png?s=75" width="75" height="75"><br /> | ||
Percy Ma | ||
</a> | ||
@@ -295,3 +296,3 @@ </td></tr></tbody></table> | ||
## <a name="sponsors"></a>Sponsors | ||
## Sponsors | ||
@@ -303,9 +304,9 @@ The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website. | ||
<h3>Platinum Sponsors</h3> | ||
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3> | ||
<p><a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="96"></a></p><h3>Silver Sponsors</h3> | ||
<p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3> | ||
<p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a></p> | ||
<p><a href="#"><img src="https://images.opencollective.com/2021-frameworks-fund/logo.png" alt="Chrome Frameworks Fund" height="undefined"></a> <a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3> | ||
<p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3> | ||
<p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://opensource.siemens.com"><img src="https://avatars.githubusercontent.com/u/624020?v=4" alt="Siemens" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3> | ||
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p> | ||
<!--sponsorsend--> | ||
## <a name="technology-sponsors"></a>Technology Sponsors | ||
## Technology Sponsors | ||
@@ -312,0 +313,0 @@ * Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
3003538
37
407
70814
300
60
+ Added@eslint/js@8.51.0
+ Addedgraphemer@^1.4.0
+ Added@eslint-community/eslint-utils@4.4.0(transitive)
+ Added@eslint-community/regexpp@4.11.0(transitive)
+ Added@eslint/eslintrc@2.1.4(transitive)
+ Added@eslint/js@8.51.0(transitive)
+ Addedgraphemer@1.4.0(transitive)
- Removedeslint-utils@^3.0.0
- Removedgrapheme-splitter@^1.0.4
- Removedimport-fresh@^3.0.0
- Removedjs-sdsl@^4.1.4
- Removedregexpp@^3.2.0
- Removedstrip-json-comments@^3.1.0
- Removed@eslint/eslintrc@1.4.1(transitive)
- Removedeslint-utils@3.0.0(transitive)
- Removedeslint-visitor-keys@2.1.0(transitive)
- Removedgrapheme-splitter@1.0.4(transitive)
- Removedjs-sdsl@4.4.2(transitive)
- Removedregexpp@3.2.0(transitive)
Updated@eslint/eslintrc@^2.1.2
Updatedajv@^6.12.4
Updatedeslint-scope@^7.2.2
Updatedeslint-visitor-keys@^3.4.3
Updatedespree@^9.6.1
Updatedesquery@^1.4.2
Updatedglobals@^13.19.0
Updatedoptionator@^0.9.3