linter.js 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406
  1. /**
  2. * @fileoverview Main Linter Class
  3. * @author Gyandeep Singh
  4. * @author aladdin-add
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const
  11. path = require("node:path"),
  12. eslintScope = require("eslint-scope"),
  13. evk = require("eslint-visitor-keys"),
  14. espree = require("espree"),
  15. merge = require("lodash.merge"),
  16. pkg = require("../../package.json"),
  17. {
  18. Legacy: {
  19. ConfigOps,
  20. ConfigValidator,
  21. environments: BuiltInEnvironments
  22. }
  23. } = require("@eslint/eslintrc/universal"),
  24. Traverser = require("../shared/traverser"),
  25. { SourceCode } = require("../languages/js/source-code"),
  26. applyDisableDirectives = require("./apply-disable-directives"),
  27. { ConfigCommentParser } = require("@eslint/plugin-kit"),
  28. NodeEventGenerator = require("./node-event-generator"),
  29. createReportTranslator = require("./report-translator"),
  30. Rules = require("./rules"),
  31. createEmitter = require("./safe-emitter"),
  32. SourceCodeFixer = require("./source-code-fixer"),
  33. timing = require("./timing"),
  34. ruleReplacements = require("../../conf/replacements.json");
  35. const { getRuleFromConfig } = require("../config/flat-config-helpers");
  36. const { FlatConfigArray } = require("../config/flat-config-array");
  37. const { startTime, endTime } = require("../shared/stats");
  38. const { RuleValidator } = require("../config/rule-validator");
  39. const { assertIsRuleSeverity } = require("../config/flat-config-schema");
  40. const { normalizeSeverityToString } = require("../shared/severity");
  41. const jslang = require("../languages/js");
  42. const { activeFlags, inactiveFlags } = require("../shared/flags");
  43. const debug = require("debug")("eslint:linter");
  44. const MAX_AUTOFIX_PASSES = 10;
  45. const DEFAULT_PARSER_NAME = "espree";
  46. const DEFAULT_ECMA_VERSION = 5;
  47. const commentParser = new ConfigCommentParser();
  48. const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
  49. const parserSymbol = Symbol.for("eslint.RuleTester.parser");
  50. const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
  51. const { VFile } = require("./vfile");
  52. const { ParserService } = require("../services/parser-service");
  53. const { FileContext } = require("./file-context");
  54. const { ProcessorService } = require("../services/processor-service");
  55. const STEP_KIND_VISIT = 1;
  56. const STEP_KIND_CALL = 2;
  57. //------------------------------------------------------------------------------
  58. // Typedefs
  59. //------------------------------------------------------------------------------
  60. /** @typedef {import("../shared/types").ConfigData} ConfigData */
  61. /** @typedef {import("../shared/types").Environment} Environment */
  62. /** @typedef {import("../shared/types").GlobalConf} GlobalConf */
  63. /** @typedef {import("../shared/types").LintMessage} LintMessage */
  64. /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
  65. /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
  66. /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
  67. /** @typedef {import("../shared/types").Processor} Processor */
  68. /** @typedef {import("../shared/types").Rule} Rule */
  69. /** @typedef {import("../shared/types").Times} Times */
  70. /** @typedef {import("@eslint/core").Language} Language */
  71. /** @typedef {import("@eslint/core").RuleSeverity} RuleSeverity */
  72. /** @typedef {import("@eslint/core").RuleConfig} RuleConfig */
  73. /* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
  74. /**
  75. * @template T
  76. * @typedef {{ [P in keyof T]-?: T[P] }} Required
  77. */
  78. /* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
  79. /**
  80. * @typedef {Object} DisableDirective
  81. * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type Type of directive
  82. * @property {number} line The line number
  83. * @property {number} column The column number
  84. * @property {(string|null)} ruleId The rule ID
  85. * @property {string} justification The justification of directive
  86. */
  87. /**
  88. * The private data for `Linter` instance.
  89. * @typedef {Object} LinterInternalSlots
  90. * @property {ConfigArray|null} lastConfigArray The `ConfigArray` instance that the last `verify()` call used.
  91. * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used.
  92. * @property {SuppressedLintMessage[]} lastSuppressedMessages The `SuppressedLintMessage[]` instance that the last `verify()` call produced.
  93. * @property {Map<string, Parser>} parserMap The loaded parsers.
  94. * @property {Times} times The times spent on applying a rule to a file (see `stats` option).
  95. * @property {Rules} ruleMap The loaded rules.
  96. */
  97. /**
  98. * @typedef {Object} VerifyOptions
  99. * @property {boolean} [allowInlineConfig] Allow/disallow inline comments' ability
  100. * to change config once it is set. Defaults to true if not supplied.
  101. * Useful if you want to validate JS without comments overriding rules.
  102. * @property {boolean} [disableFixes] if `true` then the linter doesn't make `fix`
  103. * properties into the lint result.
  104. * @property {string} [filename] the filename of the source code.
  105. * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
  106. * unused `eslint-disable` directives.
  107. * @property {Function} [ruleFilter] A predicate function that determines whether a given rule should run.
  108. */
  109. /**
  110. * @typedef {Object} ProcessorOptions
  111. * @property {(filename:string, text:string) => boolean} [filterCodeBlock] the
  112. * predicate function that selects adopt code blocks.
  113. * @property {Processor.postprocess} [postprocess] postprocessor for report
  114. * messages. If provided, this should accept an array of the message lists
  115. * for each code block returned from the preprocessor, apply a mapping to
  116. * the messages as appropriate, and return a one-dimensional array of
  117. * messages.
  118. * @property {Processor.preprocess} [preprocess] preprocessor for source text.
  119. * If provided, this should accept a string of source text, and return an
  120. * array of code blocks to lint.
  121. */
  122. /**
  123. * @typedef {Object} FixOptions
  124. * @property {boolean | ((message: LintMessage) => boolean)} [fix] Determines
  125. * whether fixes should be applied.
  126. */
  127. /**
  128. * @typedef {Object} InternalOptions
  129. * @property {string | null} warnInlineConfig The config name what `noInlineConfig` setting came from. If `noInlineConfig` setting didn't exist, this is null. If this is a config name, then the linter warns directive comments.
  130. * @property {"off" | "warn" | "error"} reportUnusedDisableDirectives (boolean values were normalized)
  131. */
  132. //------------------------------------------------------------------------------
  133. // Helpers
  134. //------------------------------------------------------------------------------
  135. /**
  136. * Determines if a given object is Espree.
  137. * @param {Object} parser The parser to check.
  138. * @returns {boolean} True if the parser is Espree or false if not.
  139. */
  140. function isEspree(parser) {
  141. return !!(parser === espree || parser[parserSymbol] === espree);
  142. }
  143. /**
  144. * Ensures that variables representing built-in properties of the Global Object,
  145. * and any globals declared by special block comments, are present in the global
  146. * scope.
  147. * @param {Scope} globalScope The global scope.
  148. * @param {Object} configGlobals The globals declared in configuration
  149. * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
  150. * @returns {void}
  151. */
  152. function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) {
  153. // Define configured global variables.
  154. for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) {
  155. /*
  156. * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
  157. * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
  158. */
  159. const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]);
  160. const commentValue = enabledGlobals[id] && enabledGlobals[id].value;
  161. const value = commentValue || configValue;
  162. const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments;
  163. if (value === "off") {
  164. continue;
  165. }
  166. let variable = globalScope.set.get(id);
  167. if (!variable) {
  168. variable = new eslintScope.Variable(id, globalScope);
  169. globalScope.variables.push(variable);
  170. globalScope.set.set(id, variable);
  171. }
  172. variable.eslintImplicitGlobalSetting = configValue;
  173. variable.eslintExplicitGlobal = sourceComments !== void 0;
  174. variable.eslintExplicitGlobalComments = sourceComments;
  175. variable.writeable = (value === "writable");
  176. }
  177. // mark all exported variables as such
  178. Object.keys(exportedVariables).forEach(name => {
  179. const variable = globalScope.set.get(name);
  180. if (variable) {
  181. variable.eslintUsed = true;
  182. variable.eslintExported = true;
  183. }
  184. });
  185. /*
  186. * "through" contains all references which definitions cannot be found.
  187. * Since we augment the global scope using configuration, we need to update
  188. * references and remove the ones that were added by configuration.
  189. */
  190. globalScope.through = globalScope.through.filter(reference => {
  191. const name = reference.identifier.name;
  192. const variable = globalScope.set.get(name);
  193. if (variable) {
  194. /*
  195. * Links the variable and the reference.
  196. * And this reference is removed from `Scope#through`.
  197. */
  198. reference.resolved = variable;
  199. variable.references.push(reference);
  200. return false;
  201. }
  202. return true;
  203. });
  204. }
  205. /**
  206. * creates a missing-rule message.
  207. * @param {string} ruleId the ruleId to create
  208. * @returns {string} created error message
  209. * @private
  210. */
  211. function createMissingRuleMessage(ruleId) {
  212. return Object.hasOwn(ruleReplacements.rules, ruleId)
  213. ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
  214. : `Definition for rule '${ruleId}' was not found.`;
  215. }
  216. /**
  217. * Updates a given location based on the language offsets. This allows us to
  218. * change 0-based locations to 1-based locations. We always want ESLint
  219. * reporting lines and columns starting from 1.
  220. * @param {Object} location The location to update.
  221. * @param {number} location.line The starting line number.
  222. * @param {number} location.column The starting column number.
  223. * @param {number} [location.endLine] The ending line number.
  224. * @param {number} [location.endColumn] The ending column number.
  225. * @param {Language} language The language to use to adjust the location information.
  226. * @returns {Object} The updated location.
  227. */
  228. function updateLocationInformation({ line, column, endLine, endColumn }, language) {
  229. const columnOffset = language.columnStart === 1 ? 0 : 1;
  230. const lineOffset = language.lineStart === 1 ? 0 : 1;
  231. // calculate separately to account for undefined
  232. const finalEndLine = endLine === void 0 ? endLine : endLine + lineOffset;
  233. const finalEndColumn = endColumn === void 0 ? endColumn : endColumn + columnOffset;
  234. return {
  235. line: line + lineOffset,
  236. column: column + columnOffset,
  237. endLine: finalEndLine,
  238. endColumn: finalEndColumn
  239. };
  240. }
  241. /**
  242. * creates a linting problem
  243. * @param {Object} options to create linting error
  244. * @param {string} [options.ruleId] the ruleId to report
  245. * @param {Object} [options.loc] the loc to report
  246. * @param {string} [options.message] the error message to report
  247. * @param {RuleSeverity} [options.severity] the error message to report
  248. * @param {Language} [options.language] the language to use to adjust the location information
  249. * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
  250. * @private
  251. */
  252. function createLintingProblem(options) {
  253. const {
  254. ruleId = null,
  255. loc = DEFAULT_ERROR_LOC,
  256. message = createMissingRuleMessage(options.ruleId),
  257. severity = 2,
  258. // fallback for eslintrc mode
  259. language = {
  260. columnStart: 0,
  261. lineStart: 1
  262. }
  263. } = options;
  264. return {
  265. ruleId,
  266. message,
  267. ...updateLocationInformation({
  268. line: loc.start.line,
  269. column: loc.start.column,
  270. endLine: loc.end.line,
  271. endColumn: loc.end.column
  272. }, language),
  273. severity,
  274. nodeType: null
  275. };
  276. }
  277. /**
  278. * Creates a collection of disable directives from a comment
  279. * @param {Object} options to create disable directives
  280. * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
  281. * @param {string} options.value The value after the directive in the comment
  282. * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
  283. * @param {string} options.justification The justification of the directive
  284. * @param {ASTNode|token} options.node The Comment node/token.
  285. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
  286. * @param {Language} language The language to use to adjust the location information.
  287. * @param {SourceCode} sourceCode The SourceCode object to get comments from.
  288. * @returns {Object} Directives and problems from the comment
  289. */
  290. function createDisableDirectives({ type, value, justification, node }, ruleMapper, language, sourceCode) {
  291. const ruleIds = Object.keys(commentParser.parseListConfig(value));
  292. const directiveRules = ruleIds.length ? ruleIds : [null];
  293. const result = {
  294. directives: [], // valid disable directives
  295. directiveProblems: [] // problems in directives
  296. };
  297. const parentDirective = { node, value, ruleIds };
  298. for (const ruleId of directiveRules) {
  299. const loc = sourceCode.getLoc(node);
  300. // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
  301. if (ruleId === null || !!ruleMapper(ruleId)) {
  302. if (type === "disable-next-line") {
  303. const { line, column } = updateLocationInformation(
  304. loc.end,
  305. language
  306. );
  307. result.directives.push({
  308. parentDirective,
  309. type,
  310. line,
  311. column,
  312. ruleId,
  313. justification
  314. });
  315. } else {
  316. const { line, column } = updateLocationInformation(
  317. loc.start,
  318. language
  319. );
  320. result.directives.push({
  321. parentDirective,
  322. type,
  323. line,
  324. column,
  325. ruleId,
  326. justification
  327. });
  328. }
  329. } else {
  330. result.directiveProblems.push(createLintingProblem({ ruleId, loc, language }));
  331. }
  332. }
  333. return result;
  334. }
  335. /**
  336. * Parses comments in file to extract file-specific config of rules, globals
  337. * and environments and merges them with global config; also code blocks
  338. * where reporting is disabled or enabled and merges them with reporting config.
  339. * @param {SourceCode} sourceCode The SourceCode object to get comments from.
  340. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
  341. * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
  342. * @param {ConfigData} config Provided config.
  343. * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}}
  344. * A collection of the directive comments that were found, along with any problems that occurred when parsing
  345. */
  346. function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) {
  347. const configuredRules = {};
  348. const enabledGlobals = Object.create(null);
  349. const exportedVariables = {};
  350. const problems = [];
  351. const disableDirectives = [];
  352. const validator = new ConfigValidator({
  353. builtInRules: Rules
  354. });
  355. sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
  356. const directive = commentParser.parseDirective(comment.value);
  357. if (!directive) {
  358. return;
  359. }
  360. const {
  361. label,
  362. value,
  363. justification: justificationPart
  364. } = directive;
  365. const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(label);
  366. if (comment.type === "Line" && !lineCommentSupported) {
  367. return;
  368. }
  369. const loc = sourceCode.getLoc(comment);
  370. if (warnInlineConfig) {
  371. const kind = comment.type === "Block" ? `/*${label}*/` : `//${label}`;
  372. problems.push(createLintingProblem({
  373. ruleId: null,
  374. message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
  375. loc,
  376. severity: 1
  377. }));
  378. return;
  379. }
  380. if (label === "eslint-disable-line" && loc.start.line !== loc.end.line) {
  381. const message = `${label} comment should not span multiple lines.`;
  382. problems.push(createLintingProblem({
  383. ruleId: null,
  384. message,
  385. loc
  386. }));
  387. return;
  388. }
  389. switch (label) {
  390. case "eslint-disable":
  391. case "eslint-enable":
  392. case "eslint-disable-next-line":
  393. case "eslint-disable-line": {
  394. const directiveType = label.slice("eslint-".length);
  395. const { directives, directiveProblems } = createDisableDirectives({
  396. type: directiveType,
  397. value,
  398. justification: justificationPart,
  399. node: comment
  400. }, ruleMapper, jslang, sourceCode);
  401. disableDirectives.push(...directives);
  402. problems.push(...directiveProblems);
  403. break;
  404. }
  405. case "exported":
  406. Object.assign(exportedVariables, commentParser.parseListConfig(value));
  407. break;
  408. case "globals":
  409. case "global":
  410. for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) {
  411. let normalizedValue;
  412. try {
  413. normalizedValue = ConfigOps.normalizeConfigGlobal(idSetting);
  414. } catch (err) {
  415. problems.push(createLintingProblem({
  416. ruleId: null,
  417. loc,
  418. message: err.message
  419. }));
  420. continue;
  421. }
  422. if (enabledGlobals[id]) {
  423. enabledGlobals[id].comments.push(comment);
  424. enabledGlobals[id].value = normalizedValue;
  425. } else {
  426. enabledGlobals[id] = {
  427. comments: [comment],
  428. value: normalizedValue
  429. };
  430. }
  431. }
  432. break;
  433. case "eslint": {
  434. const parseResult = commentParser.parseJSONLikeConfig(value);
  435. if (parseResult.ok) {
  436. Object.keys(parseResult.config).forEach(name => {
  437. const rule = ruleMapper(name);
  438. const ruleValue = parseResult.config[name];
  439. if (!rule) {
  440. problems.push(createLintingProblem({ ruleId: name, loc }));
  441. return;
  442. }
  443. if (Object.hasOwn(configuredRules, name)) {
  444. problems.push(createLintingProblem({
  445. message: `Rule "${name}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
  446. loc
  447. }));
  448. return;
  449. }
  450. let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
  451. /*
  452. * If the rule was already configured, inline rule configuration that
  453. * only has severity should retain options from the config and just override the severity.
  454. *
  455. * Example:
  456. *
  457. * {
  458. * rules: {
  459. * curly: ["error", "multi"]
  460. * }
  461. * }
  462. *
  463. * /* eslint curly: ["warn"] * /
  464. *
  465. * Results in:
  466. *
  467. * curly: ["warn", "multi"]
  468. */
  469. if (
  470. /*
  471. * If inline config for the rule has only severity
  472. */
  473. ruleOptions.length === 1 &&
  474. /*
  475. * And the rule was already configured
  476. */
  477. config.rules && Object.hasOwn(config.rules, name)
  478. ) {
  479. /*
  480. * Then use severity from the inline config and options from the provided config
  481. */
  482. ruleOptions = [
  483. ruleOptions[0], // severity from the inline config
  484. ...Array.isArray(config.rules[name]) ? config.rules[name].slice(1) : [] // options from the provided config
  485. ];
  486. }
  487. try {
  488. validator.validateRuleOptions(rule, name, ruleOptions);
  489. } catch (err) {
  490. /*
  491. * If the rule has invalid `meta.schema`, throw the error because
  492. * this is not an invalid inline configuration but an invalid rule.
  493. */
  494. if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
  495. throw err;
  496. }
  497. problems.push(createLintingProblem({
  498. ruleId: name,
  499. message: err.message,
  500. loc
  501. }));
  502. // do not apply the config, if found invalid options.
  503. return;
  504. }
  505. configuredRules[name] = ruleOptions;
  506. });
  507. } else {
  508. const problem = createLintingProblem({
  509. ruleId: null,
  510. loc,
  511. message: parseResult.error.message
  512. });
  513. problem.fatal = true;
  514. problems.push(problem);
  515. }
  516. break;
  517. }
  518. // no default
  519. }
  520. });
  521. return {
  522. configuredRules,
  523. enabledGlobals,
  524. exportedVariables,
  525. problems,
  526. disableDirectives
  527. };
  528. }
  529. /**
  530. * Parses comments in file to extract disable directives.
  531. * @param {SourceCode} sourceCode The SourceCode object to get comments from.
  532. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
  533. * @param {Language} language The language to use to adjust the location information
  534. * @returns {{problems: LintMessage[], disableDirectives: DisableDirective[]}}
  535. * A collection of the directive comments that were found, along with any problems that occurred when parsing
  536. */
  537. function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) {
  538. const disableDirectives = [];
  539. const problems = [];
  540. if (sourceCode.getDisableDirectives) {
  541. const {
  542. directives: directivesSources,
  543. problems: directivesProblems
  544. } = sourceCode.getDisableDirectives();
  545. problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({
  546. ...directiveProblem,
  547. language
  548. })));
  549. directivesSources.forEach(directive => {
  550. const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper, language, sourceCode);
  551. disableDirectives.push(...directives);
  552. problems.push(...directiveProblems);
  553. });
  554. }
  555. return {
  556. problems,
  557. disableDirectives
  558. };
  559. }
  560. /**
  561. * Normalize ECMAScript version from the initial config
  562. * @param {Parser} parser The parser which uses this options.
  563. * @param {number} ecmaVersion ECMAScript version from the initial config
  564. * @returns {number} normalized ECMAScript version
  565. */
  566. function normalizeEcmaVersion(parser, ecmaVersion) {
  567. if (isEspree(parser)) {
  568. if (ecmaVersion === "latest") {
  569. return espree.latestEcmaVersion;
  570. }
  571. }
  572. /*
  573. * Calculate ECMAScript edition number from official year version starting with
  574. * ES2015, which corresponds with ES6 (or a difference of 2009).
  575. */
  576. return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
  577. }
  578. /**
  579. * Normalize ECMAScript version from the initial config into languageOptions (year)
  580. * format.
  581. * @param {any} [ecmaVersion] ECMAScript version from the initial config
  582. * @returns {number} normalized ECMAScript version
  583. */
  584. function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
  585. switch (ecmaVersion) {
  586. case 3:
  587. return 3;
  588. // void 0 = no ecmaVersion specified so use the default
  589. case 5:
  590. case void 0:
  591. return 5;
  592. default:
  593. if (typeof ecmaVersion === "number") {
  594. return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
  595. }
  596. }
  597. /*
  598. * We default to the latest supported ecmaVersion for everything else.
  599. * Remember, this is for languageOptions.ecmaVersion, which sets the version
  600. * that is used for a number of processes inside of ESLint. It's normally
  601. * safe to assume people want the latest unless otherwise specified.
  602. */
  603. return LATEST_ECMA_VERSION;
  604. }
  605. const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
  606. /**
  607. * Checks whether or not there is a comment which has "eslint-env *" in a given text.
  608. * @param {string} text A source code text to check.
  609. * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
  610. */
  611. function findEslintEnv(text) {
  612. let match, retv;
  613. eslintEnvPattern.lastIndex = 0;
  614. while ((match = eslintEnvPattern.exec(text)) !== null) {
  615. if (match[0].endsWith("*/")) {
  616. retv = Object.assign(
  617. retv || {},
  618. commentParser.parseListConfig(commentParser.parseDirective(match[0].slice(2, -2)).value)
  619. );
  620. }
  621. }
  622. return retv;
  623. }
  624. /**
  625. * Convert "/path/to/<text>" to "<text>".
  626. * `CLIEngine#executeOnText()` method gives "/path/to/<text>" if the filename
  627. * was omitted because `configArray.extractConfig()` requires an absolute path.
  628. * But the linter should pass `<text>` to `RuleContext#filename` in that
  629. * case.
  630. * Also, code blocks can have their virtual filename. If the parent filename was
  631. * `<text>`, the virtual filename is `<text>/0_foo.js` or something like (i.e.,
  632. * it's not an absolute path).
  633. * @param {string} filename The filename to normalize.
  634. * @returns {string} The normalized filename.
  635. */
  636. function normalizeFilename(filename) {
  637. const parts = filename.split(path.sep);
  638. const index = parts.lastIndexOf("<text>");
  639. return index === -1 ? filename : parts.slice(index).join(path.sep);
  640. }
  641. /**
  642. * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
  643. * consistent shape.
  644. * @param {VerifyOptions} providedOptions Options
  645. * @param {ConfigData} config Config.
  646. * @returns {Required<VerifyOptions> & InternalOptions} Normalized options
  647. */
  648. function normalizeVerifyOptions(providedOptions, config) {
  649. const linterOptions = config.linterOptions || config;
  650. // .noInlineConfig for eslintrc, .linterOptions.noInlineConfig for flat
  651. const disableInlineConfig = linterOptions.noInlineConfig === true;
  652. const ignoreInlineConfig = providedOptions.allowInlineConfig === false;
  653. const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig
  654. ? ` (${config.configNameOfNoInlineConfig})`
  655. : "";
  656. let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives;
  657. if (typeof reportUnusedDisableDirectives === "boolean") {
  658. reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off";
  659. }
  660. if (typeof reportUnusedDisableDirectives !== "string") {
  661. if (typeof linterOptions.reportUnusedDisableDirectives === "boolean") {
  662. reportUnusedDisableDirectives = linterOptions.reportUnusedDisableDirectives ? "warn" : "off";
  663. } else {
  664. reportUnusedDisableDirectives = linterOptions.reportUnusedDisableDirectives === void 0 ? "off" : normalizeSeverityToString(linterOptions.reportUnusedDisableDirectives);
  665. }
  666. }
  667. let ruleFilter = providedOptions.ruleFilter;
  668. if (typeof ruleFilter !== "function") {
  669. ruleFilter = () => true;
  670. }
  671. return {
  672. filename: normalizeFilename(providedOptions.filename || "<input>"),
  673. allowInlineConfig: !ignoreInlineConfig,
  674. warnInlineConfig: disableInlineConfig && !ignoreInlineConfig
  675. ? `your config${configNameOfNoInlineConfig}`
  676. : null,
  677. reportUnusedDisableDirectives,
  678. disableFixes: Boolean(providedOptions.disableFixes),
  679. stats: providedOptions.stats,
  680. ruleFilter
  681. };
  682. }
  683. /**
  684. * Combines the provided parserOptions with the options from environments
  685. * @param {Parser} parser The parser which uses this options.
  686. * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
  687. * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  688. * @returns {ParserOptions} Resulting parser options after merge
  689. */
  690. function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
  691. const parserOptionsFromEnv = enabledEnvironments
  692. .filter(env => env.parserOptions)
  693. .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
  694. const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
  695. const isModule = mergedParserOptions.sourceType === "module";
  696. if (isModule) {
  697. /*
  698. * can't have global return inside of modules
  699. * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add)
  700. */
  701. mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
  702. }
  703. mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);
  704. return mergedParserOptions;
  705. }
  706. /**
  707. * Converts parserOptions to languageOptions for backwards compatibility with eslintrc.
  708. * @param {ConfigData} config Config object.
  709. * @param {Object} config.globals Global variable definitions.
  710. * @param {Parser} config.parser The parser to use.
  711. * @param {ParserOptions} config.parserOptions The parserOptions to use.
  712. * @returns {LanguageOptions} The languageOptions equivalent.
  713. */
  714. function createLanguageOptions({ globals: configuredGlobals, parser, parserOptions }) {
  715. const {
  716. ecmaVersion,
  717. sourceType
  718. } = parserOptions;
  719. return {
  720. globals: configuredGlobals,
  721. ecmaVersion: normalizeEcmaVersionForLanguageOptions(ecmaVersion),
  722. sourceType,
  723. parser,
  724. parserOptions
  725. };
  726. }
  727. /**
  728. * Combines the provided globals object with the globals from environments
  729. * @param {Record<string, GlobalConf>} providedGlobals The 'globals' key in a config
  730. * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  731. * @returns {Record<string, GlobalConf>} The resolved globals object
  732. */
  733. function resolveGlobals(providedGlobals, enabledEnvironments) {
  734. return Object.assign(
  735. Object.create(null),
  736. ...enabledEnvironments.filter(env => env.globals).map(env => env.globals),
  737. providedGlobals
  738. );
  739. }
  740. /**
  741. * Store time measurements in map
  742. * @param {number} time Time measurement
  743. * @param {Object} timeOpts Options relating which time was measured
  744. * @param {WeakMap<Linter, LinterInternalSlots>} slots Linter internal slots map
  745. * @returns {void}
  746. */
  747. function storeTime(time, timeOpts, slots) {
  748. const { type, key } = timeOpts;
  749. if (!slots.times) {
  750. slots.times = { passes: [{}] };
  751. }
  752. const passIndex = slots.fixPasses;
  753. if (passIndex > slots.times.passes.length - 1) {
  754. slots.times.passes.push({});
  755. }
  756. if (key) {
  757. slots.times.passes[passIndex][type] ??= {};
  758. slots.times.passes[passIndex][type][key] ??= { total: 0 };
  759. slots.times.passes[passIndex][type][key].total += time;
  760. } else {
  761. slots.times.passes[passIndex][type] ??= { total: 0 };
  762. slots.times.passes[passIndex][type].total += time;
  763. }
  764. }
  765. /**
  766. * Get the options for a rule (not including severity), if any
  767. * @param {RuleConfig} ruleConfig rule configuration
  768. * @returns {Array} of rule options, empty Array if none
  769. */
  770. function getRuleOptions(ruleConfig) {
  771. if (Array.isArray(ruleConfig)) {
  772. return ruleConfig.slice(1);
  773. }
  774. return [];
  775. }
  776. /**
  777. * Analyze scope of the given AST.
  778. * @param {ASTNode} ast The `Program` node to analyze.
  779. * @param {LanguageOptions} languageOptions The parser options.
  780. * @param {Record<string, string[]>} visitorKeys The visitor keys.
  781. * @returns {ScopeManager} The analysis result.
  782. */
  783. function analyzeScope(ast, languageOptions, visitorKeys) {
  784. const parserOptions = languageOptions.parserOptions;
  785. const ecmaFeatures = parserOptions.ecmaFeatures || {};
  786. const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
  787. return eslintScope.analyze(ast, {
  788. ignoreEval: true,
  789. nodejsScope: ecmaFeatures.globalReturn,
  790. impliedStrict: ecmaFeatures.impliedStrict,
  791. ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
  792. sourceType: languageOptions.sourceType || "script",
  793. childVisitorKeys: visitorKeys || evk.KEYS,
  794. fallback: Traverser.getKeys
  795. });
  796. }
  797. /**
  798. * Runs a rule, and gets its listeners
  799. * @param {Rule} rule A rule object
  800. * @param {Context} ruleContext The context that should be passed to the rule
  801. * @throws {TypeError} If `rule` is not an object with a `create` method
  802. * @throws {any} Any error during the rule's `create`
  803. * @returns {Object} A map of selector listeners provided by the rule
  804. */
  805. function createRuleListeners(rule, ruleContext) {
  806. if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
  807. throw new TypeError(`Error while loading rule '${ruleContext.id}': Rule must be an object with a \`create\` method`);
  808. }
  809. try {
  810. return rule.create(ruleContext);
  811. } catch (ex) {
  812. ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`;
  813. throw ex;
  814. }
  815. }
  816. /**
  817. * Runs the given rules on the given SourceCode object
  818. * @param {SourceCode} sourceCode A SourceCode object for the given text
  819. * @param {Object} configuredRules The rules configuration
  820. * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
  821. * @param {string | undefined} parserName The name of the parser in the config
  822. * @param {Language} language The language object used for parsing.
  823. * @param {LanguageOptions} languageOptions The options for parsing the code.
  824. * @param {Object} settings The settings that were enabled in the config
  825. * @param {string} filename The reported filename of the code
  826. * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
  827. * @param {string | undefined} cwd cwd of the cli
  828. * @param {string} physicalFilename The full path of the file on disk without any code block information
  829. * @param {Function} ruleFilter A predicate function to filter which rules should be executed.
  830. * @param {boolean} stats If true, stats are collected appended to the result
  831. * @param {WeakMap<Linter, LinterInternalSlots>} slots InternalSlotsMap of linter
  832. * @returns {LintMessage[]} An array of reported problems
  833. * @throws {Error} If traversal into a node fails.
  834. */
  835. function runRules(
  836. sourceCode, configuredRules, ruleMapper, parserName, language, languageOptions,
  837. settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
  838. stats, slots
  839. ) {
  840. const emitter = createEmitter();
  841. // must happen first to assign all node.parent properties
  842. const eventQueue = sourceCode.traverse();
  843. /*
  844. * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
  845. * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
  846. * properties once for each rule.
  847. */
  848. const sharedTraversalContext = new FileContext({
  849. cwd,
  850. filename,
  851. physicalFilename: physicalFilename || filename,
  852. sourceCode,
  853. parserOptions: {
  854. ...languageOptions.parserOptions
  855. },
  856. parserPath: parserName,
  857. languageOptions,
  858. settings
  859. });
  860. const lintingProblems = [];
  861. Object.keys(configuredRules).forEach(ruleId => {
  862. const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
  863. // not load disabled rules
  864. if (severity === 0) {
  865. return;
  866. }
  867. if (ruleFilter && !ruleFilter({ ruleId, severity })) {
  868. return;
  869. }
  870. const rule = ruleMapper(ruleId);
  871. if (!rule) {
  872. lintingProblems.push(createLintingProblem({ ruleId, language }));
  873. return;
  874. }
  875. const messageIds = rule.meta && rule.meta.messages;
  876. let reportTranslator = null;
  877. const ruleContext = Object.freeze(
  878. Object.assign(
  879. Object.create(sharedTraversalContext),
  880. {
  881. id: ruleId,
  882. options: getRuleOptions(configuredRules[ruleId]),
  883. report(...args) {
  884. /*
  885. * Create a report translator lazily.
  886. * In a vast majority of cases, any given rule reports zero errors on a given
  887. * piece of code. Creating a translator lazily avoids the performance cost of
  888. * creating a new translator function for each rule that usually doesn't get
  889. * called.
  890. *
  891. * Using lazy report translators improves end-to-end performance by about 3%
  892. * with Node 8.4.0.
  893. */
  894. if (reportTranslator === null) {
  895. reportTranslator = createReportTranslator({
  896. ruleId,
  897. severity,
  898. sourceCode,
  899. messageIds,
  900. disableFixes,
  901. language
  902. });
  903. }
  904. const problem = reportTranslator(...args);
  905. if (problem.fix && !(rule.meta && rule.meta.fixable)) {
  906. throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
  907. }
  908. if (problem.suggestions && !(rule.meta && rule.meta.hasSuggestions === true)) {
  909. if (rule.meta && rule.meta.docs && typeof rule.meta.docs.suggestion !== "undefined") {
  910. // Encourage migration from the former property name.
  911. throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.");
  912. }
  913. throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
  914. }
  915. lintingProblems.push(problem);
  916. }
  917. }
  918. )
  919. );
  920. const ruleListenersReturn = (timing.enabled || stats)
  921. ? timing.time(ruleId, createRuleListeners, stats)(rule, ruleContext) : createRuleListeners(rule, ruleContext);
  922. const ruleListeners = stats ? ruleListenersReturn.result : ruleListenersReturn;
  923. if (stats) {
  924. storeTime(ruleListenersReturn.tdiff, { type: "rules", key: ruleId }, slots);
  925. }
  926. /**
  927. * Include `ruleId` in error logs
  928. * @param {Function} ruleListener A rule method that listens for a node.
  929. * @returns {Function} ruleListener wrapped in error handler
  930. */
  931. function addRuleErrorHandler(ruleListener) {
  932. return function ruleErrorHandler(...listenerArgs) {
  933. try {
  934. const ruleListenerReturn = ruleListener(...listenerArgs);
  935. const ruleListenerResult = stats ? ruleListenerReturn.result : ruleListenerReturn;
  936. if (stats) {
  937. storeTime(ruleListenerReturn.tdiff, { type: "rules", key: ruleId }, slots);
  938. }
  939. return ruleListenerResult;
  940. } catch (e) {
  941. e.ruleId = ruleId;
  942. throw e;
  943. }
  944. };
  945. }
  946. if (typeof ruleListeners === "undefined" || ruleListeners === null) {
  947. throw new Error(`The create() function for rule '${ruleId}' did not return an object.`);
  948. }
  949. // add all the selectors from the rule as listeners
  950. Object.keys(ruleListeners).forEach(selector => {
  951. const ruleListener = (timing.enabled || stats)
  952. ? timing.time(ruleId, ruleListeners[selector], stats) : ruleListeners[selector];
  953. emitter.on(
  954. selector,
  955. addRuleErrorHandler(ruleListener)
  956. );
  957. });
  958. });
  959. const eventGenerator = new NodeEventGenerator(emitter, {
  960. visitorKeys: sourceCode.visitorKeys ?? language.visitorKeys,
  961. fallback: Traverser.getKeys,
  962. matchClass: language.matchesSelectorClass ?? (() => false),
  963. nodeTypeKey: language.nodeTypeKey
  964. });
  965. for (const step of eventQueue) {
  966. switch (step.kind) {
  967. case STEP_KIND_VISIT: {
  968. try {
  969. if (step.phase === 1) {
  970. eventGenerator.enterNode(step.target);
  971. } else {
  972. eventGenerator.leaveNode(step.target);
  973. }
  974. } catch (err) {
  975. err.currentNode = step.target;
  976. throw err;
  977. }
  978. break;
  979. }
  980. case STEP_KIND_CALL: {
  981. emitter.emit(step.target, ...step.args);
  982. break;
  983. }
  984. default:
  985. throw new Error(`Invalid traversal step found: "${step.type}".`);
  986. }
  987. }
  988. return lintingProblems;
  989. }
  990. /**
  991. * Ensure the source code to be a string.
  992. * @param {string|SourceCode} textOrSourceCode The text or source code object.
  993. * @returns {string} The source code text.
  994. */
  995. function ensureText(textOrSourceCode) {
  996. if (typeof textOrSourceCode === "object") {
  997. const { hasBOM, text } = textOrSourceCode;
  998. const bom = hasBOM ? "\uFEFF" : "";
  999. return bom + text;
  1000. }
  1001. return String(textOrSourceCode);
  1002. }
  1003. /**
  1004. * Get an environment.
  1005. * @param {LinterInternalSlots} slots The internal slots of Linter.
  1006. * @param {string} envId The environment ID to get.
  1007. * @returns {Environment|null} The environment.
  1008. */
  1009. function getEnv(slots, envId) {
  1010. return (
  1011. (slots.lastConfigArray && slots.lastConfigArray.pluginEnvironments.get(envId)) ||
  1012. BuiltInEnvironments.get(envId) ||
  1013. null
  1014. );
  1015. }
  1016. /**
  1017. * Get a rule.
  1018. * @param {LinterInternalSlots} slots The internal slots of Linter.
  1019. * @param {string} ruleId The rule ID to get.
  1020. * @returns {Rule|null} The rule.
  1021. */
  1022. function getRule(slots, ruleId) {
  1023. return (
  1024. (slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) ||
  1025. slots.ruleMap.get(ruleId)
  1026. );
  1027. }
  1028. /**
  1029. * Normalize the value of the cwd
  1030. * @param {string | undefined} cwd raw value of the cwd, path to a directory that should be considered as the current working directory, can be undefined.
  1031. * @returns {string | undefined} normalized cwd
  1032. */
  1033. function normalizeCwd(cwd) {
  1034. if (cwd) {
  1035. return cwd;
  1036. }
  1037. if (typeof process === "object") {
  1038. return process.cwd();
  1039. }
  1040. // It's more explicit to assign the undefined
  1041. // eslint-disable-next-line no-undefined -- Consistently returning a value
  1042. return undefined;
  1043. }
  1044. /**
  1045. * The map to store private data.
  1046. * @type {WeakMap<Linter, LinterInternalSlots>}
  1047. */
  1048. const internalSlotsMap = new WeakMap();
  1049. /**
  1050. * Throws an error when the given linter is in flat config mode.
  1051. * @param {Linter} linter The linter to check.
  1052. * @returns {void}
  1053. * @throws {Error} If the linter is in flat config mode.
  1054. */
  1055. function assertEslintrcConfig(linter) {
  1056. const { configType } = internalSlotsMap.get(linter);
  1057. if (configType === "flat") {
  1058. throw new Error("This method cannot be used with flat config. Add your entries directly into the config array.");
  1059. }
  1060. }
  1061. //------------------------------------------------------------------------------
  1062. // Public Interface
  1063. //------------------------------------------------------------------------------
  1064. /**
  1065. * Object that is responsible for verifying JavaScript text
  1066. * @name Linter
  1067. */
  1068. class Linter {
  1069. /**
  1070. * Initialize the Linter.
  1071. * @param {Object} [config] the config object
  1072. * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
  1073. * @param {Array<string>} [config.flags] the feature flags to enable.
  1074. * @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used.
  1075. */
  1076. constructor({ cwd, configType = "flat", flags = [] } = {}) {
  1077. flags.forEach(flag => {
  1078. if (inactiveFlags.has(flag)) {
  1079. throw new Error(`The flag '${flag}' is inactive: ${inactiveFlags.get(flag)}`);
  1080. }
  1081. if (!activeFlags.has(flag)) {
  1082. throw new Error(`Unknown flag '${flag}'.`);
  1083. }
  1084. });
  1085. internalSlotsMap.set(this, {
  1086. cwd: normalizeCwd(cwd),
  1087. flags,
  1088. lastConfigArray: null,
  1089. lastSourceCode: null,
  1090. lastSuppressedMessages: [],
  1091. configType, // TODO: Remove after flat config conversion
  1092. parserMap: new Map([["espree", espree]]),
  1093. ruleMap: new Rules()
  1094. });
  1095. this.version = pkg.version;
  1096. }
  1097. /**
  1098. * Getter for package version.
  1099. * @static
  1100. * @returns {string} The version from package.json.
  1101. */
  1102. static get version() {
  1103. return pkg.version;
  1104. }
  1105. /**
  1106. * Indicates if the given feature flag is enabled for this instance.
  1107. * @param {string} flag The feature flag to check.
  1108. * @returns {boolean} `true` if the feature flag is enabled, `false` if not.
  1109. */
  1110. hasFlag(flag) {
  1111. return internalSlotsMap.get(this).flags.includes(flag);
  1112. }
  1113. /**
  1114. * Lint using eslintrc and without processors.
  1115. * @param {VFile} file The file to lint.
  1116. * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
  1117. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
  1118. * @throws {Error} If during rule execution.
  1119. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
  1120. */
  1121. #eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions) {
  1122. const slots = internalSlotsMap.get(this);
  1123. const config = providedConfig || {};
  1124. const options = normalizeVerifyOptions(providedOptions, config);
  1125. // Resolve parser.
  1126. let parserName = DEFAULT_PARSER_NAME;
  1127. let parser = espree;
  1128. if (typeof config.parser === "object" && config.parser !== null) {
  1129. parserName = config.parser.filePath;
  1130. parser = config.parser.definition;
  1131. } else if (typeof config.parser === "string") {
  1132. if (!slots.parserMap.has(config.parser)) {
  1133. return [{
  1134. ruleId: null,
  1135. fatal: true,
  1136. severity: 2,
  1137. message: `Configured parser '${config.parser}' was not found.`,
  1138. line: 0,
  1139. column: 0,
  1140. nodeType: null
  1141. }];
  1142. }
  1143. parserName = config.parser;
  1144. parser = slots.parserMap.get(config.parser);
  1145. }
  1146. // search and apply "eslint-env *".
  1147. const envInFile = options.allowInlineConfig && !options.warnInlineConfig
  1148. ? findEslintEnv(file.body)
  1149. : {};
  1150. const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
  1151. const enabledEnvs = Object.keys(resolvedEnvConfig)
  1152. .filter(envName => resolvedEnvConfig[envName])
  1153. .map(envName => getEnv(slots, envName))
  1154. .filter(env => env);
  1155. const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
  1156. const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
  1157. const settings = config.settings || {};
  1158. const languageOptions = createLanguageOptions({
  1159. globals: config.globals,
  1160. parser,
  1161. parserOptions
  1162. });
  1163. if (!slots.lastSourceCode) {
  1164. let t;
  1165. if (options.stats) {
  1166. t = startTime();
  1167. }
  1168. const parserService = new ParserService();
  1169. const parseResult = parserService.parseSync(
  1170. file,
  1171. {
  1172. language: jslang,
  1173. languageOptions
  1174. }
  1175. );
  1176. if (options.stats) {
  1177. const time = endTime(t);
  1178. const timeOpts = { type: "parse" };
  1179. storeTime(time, timeOpts, slots);
  1180. }
  1181. if (!parseResult.ok) {
  1182. return parseResult.errors;
  1183. }
  1184. slots.lastSourceCode = parseResult.sourceCode;
  1185. } else {
  1186. /*
  1187. * If the given source code object as the first argument does not have scopeManager, analyze the scope.
  1188. * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
  1189. */
  1190. if (!slots.lastSourceCode.scopeManager) {
  1191. slots.lastSourceCode = new SourceCode({
  1192. text: slots.lastSourceCode.text,
  1193. ast: slots.lastSourceCode.ast,
  1194. hasBOM: slots.lastSourceCode.hasBOM,
  1195. parserServices: slots.lastSourceCode.parserServices,
  1196. visitorKeys: slots.lastSourceCode.visitorKeys,
  1197. scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
  1198. });
  1199. }
  1200. }
  1201. const sourceCode = slots.lastSourceCode;
  1202. const commentDirectives = options.allowInlineConfig
  1203. ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config)
  1204. : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
  1205. addDeclaredGlobals(
  1206. sourceCode.scopeManager.scopes[0],
  1207. configuredGlobals,
  1208. { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
  1209. );
  1210. const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
  1211. let lintingProblems;
  1212. try {
  1213. lintingProblems = runRules(
  1214. sourceCode,
  1215. configuredRules,
  1216. ruleId => getRule(slots, ruleId),
  1217. parserName,
  1218. jslang,
  1219. languageOptions,
  1220. settings,
  1221. options.filename,
  1222. options.disableFixes,
  1223. slots.cwd,
  1224. providedOptions.physicalFilename,
  1225. null,
  1226. options.stats,
  1227. slots
  1228. );
  1229. } catch (err) {
  1230. err.message += `\nOccurred while linting ${options.filename}`;
  1231. debug("An error occurred while traversing");
  1232. debug("Filename:", options.filename);
  1233. if (err.currentNode) {
  1234. const { line } = sourceCode.getLoc(err.currentNode).start;
  1235. debug("Line:", line);
  1236. err.message += `:${line}`;
  1237. }
  1238. debug("Parser Options:", parserOptions);
  1239. debug("Parser Path:", parserName);
  1240. debug("Settings:", settings);
  1241. if (err.ruleId) {
  1242. err.message += `\nRule: "${err.ruleId}"`;
  1243. }
  1244. throw err;
  1245. }
  1246. return applyDisableDirectives({
  1247. language: jslang,
  1248. sourceCode,
  1249. directives: commentDirectives.disableDirectives,
  1250. disableFixes: options.disableFixes,
  1251. problems: lintingProblems
  1252. .concat(commentDirectives.problems)
  1253. .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
  1254. reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
  1255. });
  1256. }
  1257. /**
  1258. * Same as linter.verify, except without support for processors.
  1259. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  1260. * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
  1261. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
  1262. * @throws {Error} If during rule execution.
  1263. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
  1264. */
  1265. _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
  1266. const slots = internalSlotsMap.get(this);
  1267. const filename = normalizeFilename(providedOptions.filename || "<input>");
  1268. let text;
  1269. // evaluate arguments
  1270. if (typeof textOrSourceCode === "string") {
  1271. slots.lastSourceCode = null;
  1272. text = textOrSourceCode;
  1273. } else {
  1274. slots.lastSourceCode = textOrSourceCode;
  1275. text = textOrSourceCode.text;
  1276. }
  1277. const file = new VFile(filename, text, {
  1278. physicalPath: providedOptions.physicalFilename
  1279. });
  1280. return this.#eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions);
  1281. }
  1282. /**
  1283. * Verifies the text against the rules specified by the second argument.
  1284. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  1285. * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything.
  1286. * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked.
  1287. * If this is not set, the filename will default to '<input>' in the rule context. If
  1288. * an object, then it has "filename", "allowInlineConfig", and some properties.
  1289. * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
  1290. */
  1291. verify(textOrSourceCode, config, filenameOrOptions) {
  1292. debug("Verify");
  1293. const { configType, cwd } = internalSlotsMap.get(this);
  1294. const options = typeof filenameOrOptions === "string"
  1295. ? { filename: filenameOrOptions }
  1296. : filenameOrOptions || {};
  1297. const configToUse = config ?? {};
  1298. if (configType !== "eslintrc") {
  1299. /*
  1300. * Because of how Webpack packages up the files, we can't
  1301. * compare directly to `FlatConfigArray` using `instanceof`
  1302. * because it's not the same `FlatConfigArray` as in the tests.
  1303. * So, we work around it by assuming an array is, in fact, a
  1304. * `FlatConfigArray` if it has a `getConfig()` method.
  1305. */
  1306. let configArray = configToUse;
  1307. if (!Array.isArray(configToUse) || typeof configToUse.getConfig !== "function") {
  1308. configArray = new FlatConfigArray(configToUse, { basePath: cwd });
  1309. configArray.normalizeSync();
  1310. }
  1311. return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true));
  1312. }
  1313. if (typeof configToUse.extractConfig === "function") {
  1314. return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, configToUse, options));
  1315. }
  1316. /*
  1317. * If we get to here, it means `config` is just an object rather
  1318. * than a config array so we can go right into linting.
  1319. */
  1320. /*
  1321. * `Linter` doesn't support `overrides` property in configuration.
  1322. * So we cannot apply multiple processors.
  1323. */
  1324. if (options.preprocess || options.postprocess) {
  1325. return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, configToUse, options));
  1326. }
  1327. return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, configToUse, options));
  1328. }
  1329. /**
  1330. * Verify with a processor.
  1331. * @param {string|SourceCode} textOrSourceCode The source code.
  1332. * @param {FlatConfig} config The config array.
  1333. * @param {VerifyOptions&ProcessorOptions} options The options.
  1334. * @param {FlatConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
  1335. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1336. */
  1337. _verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) {
  1338. const slots = internalSlotsMap.get(this);
  1339. const filename = options.filename || "<input>";
  1340. const filenameToExpose = normalizeFilename(filename);
  1341. const physicalFilename = options.physicalFilename || filenameToExpose;
  1342. const text = ensureText(textOrSourceCode);
  1343. const file = new VFile(filenameToExpose, text, {
  1344. physicalPath: physicalFilename
  1345. });
  1346. const preprocess = options.preprocess || (rawText => [rawText]);
  1347. const postprocess = options.postprocess || (messagesList => messagesList.flat());
  1348. const processorService = new ProcessorService();
  1349. const preprocessResult = processorService.preprocessSync(file, {
  1350. processor: {
  1351. preprocess,
  1352. postprocess
  1353. }
  1354. });
  1355. if (!preprocessResult.ok) {
  1356. return preprocessResult.errors;
  1357. }
  1358. const filterCodeBlock =
  1359. options.filterCodeBlock ||
  1360. (blockFilename => blockFilename.endsWith(".js"));
  1361. const originalExtname = path.extname(filename);
  1362. const { files } = preprocessResult;
  1363. const messageLists = files.map(block => {
  1364. debug("A code block was found: %o", block.path || "(unnamed)");
  1365. // Keep the legacy behavior.
  1366. if (typeof block === "string") {
  1367. return this._verifyWithFlatConfigArrayAndWithoutProcessors(block, config, options);
  1368. }
  1369. // Skip this block if filtered.
  1370. if (!filterCodeBlock(block.path, block.body)) {
  1371. debug("This code block was skipped.");
  1372. return [];
  1373. }
  1374. // Resolve configuration again if the file content or extension was changed.
  1375. if (configForRecursive && (text !== block.rawBody || path.extname(block.path) !== originalExtname)) {
  1376. debug("Resolving configuration again because the file content or extension was changed.");
  1377. return this._verifyWithFlatConfigArray(
  1378. block.rawBody,
  1379. configForRecursive,
  1380. { ...options, filename: block.path, physicalFilename: block.physicalPath }
  1381. );
  1382. }
  1383. slots.lastSourceCode = null;
  1384. // Does lint.
  1385. return this.#flatVerifyWithoutProcessors(
  1386. block,
  1387. config,
  1388. { ...options, filename: block.path, physicalFilename: block.physicalPath }
  1389. );
  1390. });
  1391. return processorService.postprocessSync(file, messageLists, {
  1392. processor: {
  1393. preprocess,
  1394. postprocess
  1395. }
  1396. });
  1397. }
  1398. /**
  1399. * Verify using flat config and without any processors.
  1400. * @param {VFile} file The file to lint.
  1401. * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything.
  1402. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
  1403. * @throws {Error} If during rule execution.
  1404. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
  1405. */
  1406. #flatVerifyWithoutProcessors(file, providedConfig, providedOptions) {
  1407. const slots = internalSlotsMap.get(this);
  1408. const config = providedConfig || {};
  1409. const options = normalizeVerifyOptions(providedOptions, config);
  1410. const languageOptions = config.languageOptions;
  1411. if (config.language === jslang) {
  1412. languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
  1413. languageOptions.ecmaVersion
  1414. );
  1415. // Espree expects this information to be passed in
  1416. if (isEspree(languageOptions.parser)) {
  1417. const parserOptions = languageOptions.parserOptions;
  1418. if (languageOptions.sourceType) {
  1419. parserOptions.sourceType = languageOptions.sourceType;
  1420. if (
  1421. parserOptions.sourceType === "module" &&
  1422. parserOptions.ecmaFeatures &&
  1423. parserOptions.ecmaFeatures.globalReturn
  1424. ) {
  1425. parserOptions.ecmaFeatures.globalReturn = false;
  1426. }
  1427. }
  1428. }
  1429. }
  1430. const settings = config.settings || {};
  1431. if (!slots.lastSourceCode) {
  1432. let t;
  1433. if (options.stats) {
  1434. t = startTime();
  1435. }
  1436. const parserService = new ParserService();
  1437. const parseResult = parserService.parseSync(
  1438. file,
  1439. config
  1440. );
  1441. if (options.stats) {
  1442. const time = endTime(t);
  1443. storeTime(time, { type: "parse" }, slots);
  1444. }
  1445. if (!parseResult.ok) {
  1446. return parseResult.errors;
  1447. }
  1448. slots.lastSourceCode = parseResult.sourceCode;
  1449. } else {
  1450. /*
  1451. * If the given source code object as the first argument does not have scopeManager, analyze the scope.
  1452. * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
  1453. *
  1454. * We check explicitly for `null` to ensure that this is a JS-flavored language.
  1455. * For non-JS languages we don't want to do this.
  1456. *
  1457. * TODO: Remove this check when we stop exporting the `SourceCode` object.
  1458. */
  1459. if (slots.lastSourceCode.scopeManager === null) {
  1460. slots.lastSourceCode = new SourceCode({
  1461. text: slots.lastSourceCode.text,
  1462. ast: slots.lastSourceCode.ast,
  1463. hasBOM: slots.lastSourceCode.hasBOM,
  1464. parserServices: slots.lastSourceCode.parserServices,
  1465. visitorKeys: slots.lastSourceCode.visitorKeys,
  1466. scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
  1467. });
  1468. }
  1469. }
  1470. const sourceCode = slots.lastSourceCode;
  1471. /*
  1472. * Make adjustments based on the language options. For JavaScript,
  1473. * this is primarily about adding variables into the global scope
  1474. * to account for ecmaVersion and configured globals.
  1475. */
  1476. sourceCode.applyLanguageOptions?.(languageOptions);
  1477. const mergedInlineConfig = {
  1478. rules: {}
  1479. };
  1480. const inlineConfigProblems = [];
  1481. /*
  1482. * Inline config can be either enabled or disabled. If disabled, it's possible
  1483. * to detect the inline config and emit a warning (though this is not required).
  1484. * So we first check to see if inline config is allowed at all, and if so, we
  1485. * need to check if it's a warning or not.
  1486. */
  1487. if (options.allowInlineConfig) {
  1488. // if inline config should warn then add the warnings
  1489. if (options.warnInlineConfig) {
  1490. if (sourceCode.getInlineConfigNodes) {
  1491. sourceCode.getInlineConfigNodes().forEach(node => {
  1492. const loc = sourceCode.getLoc(node);
  1493. const range = sourceCode.getRange(node);
  1494. inlineConfigProblems.push(createLintingProblem({
  1495. ruleId: null,
  1496. message: `'${sourceCode.text.slice(range[0], range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
  1497. loc,
  1498. severity: 1,
  1499. language: config.language
  1500. }));
  1501. });
  1502. }
  1503. } else {
  1504. const inlineConfigResult = sourceCode.applyInlineConfig?.();
  1505. if (inlineConfigResult) {
  1506. inlineConfigProblems.push(
  1507. ...inlineConfigResult.problems
  1508. .map(problem => createLintingProblem({ ...problem, language: config.language }))
  1509. .map(problem => {
  1510. problem.fatal = true;
  1511. return problem;
  1512. })
  1513. );
  1514. // next we need to verify information about the specified rules
  1515. const ruleValidator = new RuleValidator();
  1516. for (const { config: inlineConfig, loc } of inlineConfigResult.configs) {
  1517. Object.keys(inlineConfig.rules).forEach(ruleId => {
  1518. const rule = getRuleFromConfig(ruleId, config);
  1519. const ruleValue = inlineConfig.rules[ruleId];
  1520. if (!rule) {
  1521. inlineConfigProblems.push(createLintingProblem({
  1522. ruleId,
  1523. loc,
  1524. language: config.language
  1525. }));
  1526. return;
  1527. }
  1528. if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
  1529. inlineConfigProblems.push(createLintingProblem({
  1530. message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
  1531. loc,
  1532. language: config.language
  1533. }));
  1534. return;
  1535. }
  1536. try {
  1537. let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
  1538. assertIsRuleSeverity(ruleId, ruleOptions[0]);
  1539. /*
  1540. * If the rule was already configured, inline rule configuration that
  1541. * only has severity should retain options from the config and just override the severity.
  1542. *
  1543. * Example:
  1544. *
  1545. * {
  1546. * rules: {
  1547. * curly: ["error", "multi"]
  1548. * }
  1549. * }
  1550. *
  1551. * /* eslint curly: ["warn"] * /
  1552. *
  1553. * Results in:
  1554. *
  1555. * curly: ["warn", "multi"]
  1556. */
  1557. let shouldValidateOptions = true;
  1558. if (
  1559. /*
  1560. * If inline config for the rule has only severity
  1561. */
  1562. ruleOptions.length === 1 &&
  1563. /*
  1564. * And the rule was already configured
  1565. */
  1566. config.rules && Object.hasOwn(config.rules, ruleId)
  1567. ) {
  1568. /*
  1569. * Then use severity from the inline config and options from the provided config
  1570. */
  1571. ruleOptions = [
  1572. ruleOptions[0], // severity from the inline config
  1573. ...config.rules[ruleId].slice(1) // options from the provided config
  1574. ];
  1575. // if the rule was enabled, the options have already been validated
  1576. if (config.rules[ruleId][0] > 0) {
  1577. shouldValidateOptions = false;
  1578. }
  1579. }
  1580. if (shouldValidateOptions) {
  1581. ruleValidator.validate({
  1582. plugins: config.plugins,
  1583. rules: {
  1584. [ruleId]: ruleOptions
  1585. }
  1586. });
  1587. }
  1588. mergedInlineConfig.rules[ruleId] = ruleOptions;
  1589. } catch (err) {
  1590. /*
  1591. * If the rule has invalid `meta.schema`, throw the error because
  1592. * this is not an invalid inline configuration but an invalid rule.
  1593. */
  1594. if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
  1595. throw err;
  1596. }
  1597. let baseMessage = err.message.slice(
  1598. err.message.startsWith("Key \"rules\":")
  1599. ? err.message.indexOf(":", 12) + 1
  1600. : err.message.indexOf(":") + 1
  1601. ).trim();
  1602. if (err.messageTemplate) {
  1603. baseMessage += ` You passed "${ruleValue}".`;
  1604. }
  1605. inlineConfigProblems.push(createLintingProblem({
  1606. ruleId,
  1607. message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
  1608. loc,
  1609. language: config.language
  1610. }));
  1611. }
  1612. });
  1613. }
  1614. }
  1615. }
  1616. }
  1617. const commentDirectives = options.allowInlineConfig && !options.warnInlineConfig
  1618. ? getDirectiveCommentsForFlatConfig(
  1619. sourceCode,
  1620. ruleId => getRuleFromConfig(ruleId, config),
  1621. config.language
  1622. )
  1623. : { problems: [], disableDirectives: [] };
  1624. const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
  1625. let lintingProblems;
  1626. sourceCode.finalize?.();
  1627. try {
  1628. lintingProblems = runRules(
  1629. sourceCode,
  1630. configuredRules,
  1631. ruleId => getRuleFromConfig(ruleId, config),
  1632. void 0,
  1633. config.language,
  1634. languageOptions,
  1635. settings,
  1636. options.filename,
  1637. options.disableFixes,
  1638. slots.cwd,
  1639. providedOptions.physicalFilename,
  1640. options.ruleFilter,
  1641. options.stats,
  1642. slots
  1643. );
  1644. } catch (err) {
  1645. err.message += `\nOccurred while linting ${options.filename}`;
  1646. debug("An error occurred while traversing");
  1647. debug("Filename:", options.filename);
  1648. if (err.currentNode) {
  1649. const { line } = sourceCode.getLoc(err.currentNode).start;
  1650. debug("Line:", line);
  1651. err.message += `:${line}`;
  1652. }
  1653. debug("Parser Options:", languageOptions.parserOptions);
  1654. // debug("Parser Path:", parserName);
  1655. debug("Settings:", settings);
  1656. if (err.ruleId) {
  1657. err.message += `\nRule: "${err.ruleId}"`;
  1658. }
  1659. throw err;
  1660. }
  1661. return applyDisableDirectives({
  1662. language: config.language,
  1663. sourceCode,
  1664. directives: commentDirectives.disableDirectives,
  1665. disableFixes: options.disableFixes,
  1666. problems: lintingProblems
  1667. .concat(commentDirectives.problems)
  1668. .concat(inlineConfigProblems)
  1669. .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
  1670. reportUnusedDisableDirectives: options.reportUnusedDisableDirectives,
  1671. ruleFilter: options.ruleFilter,
  1672. configuredRules
  1673. });
  1674. }
  1675. /**
  1676. * Same as linter.verify, except without support for processors.
  1677. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  1678. * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything.
  1679. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
  1680. * @throws {Error} If during rule execution.
  1681. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
  1682. */
  1683. _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
  1684. const slots = internalSlotsMap.get(this);
  1685. const filename = normalizeFilename(providedOptions.filename || "<input>");
  1686. let text;
  1687. // evaluate arguments
  1688. if (typeof textOrSourceCode === "string") {
  1689. slots.lastSourceCode = null;
  1690. text = textOrSourceCode;
  1691. } else {
  1692. slots.lastSourceCode = textOrSourceCode;
  1693. text = textOrSourceCode.text;
  1694. }
  1695. const file = new VFile(filename, text, {
  1696. physicalPath: providedOptions.physicalFilename
  1697. });
  1698. return this.#flatVerifyWithoutProcessors(file, providedConfig, providedOptions);
  1699. }
  1700. /**
  1701. * Verify a given code with `ConfigArray`.
  1702. * @param {string|SourceCode} textOrSourceCode The source code.
  1703. * @param {ConfigArray} configArray The config array.
  1704. * @param {VerifyOptions&ProcessorOptions} options The options.
  1705. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1706. */
  1707. _verifyWithConfigArray(textOrSourceCode, configArray, options) {
  1708. debug("With ConfigArray: %s", options.filename);
  1709. // Store the config array in order to get plugin envs and rules later.
  1710. internalSlotsMap.get(this).lastConfigArray = configArray;
  1711. // Extract the final config for this file.
  1712. const config = configArray.extractConfig(options.filename);
  1713. const processor =
  1714. config.processor &&
  1715. configArray.pluginProcessors.get(config.processor);
  1716. // Verify.
  1717. if (processor) {
  1718. debug("Apply the processor: %o", config.processor);
  1719. const { preprocess, postprocess, supportsAutofix } = processor;
  1720. const disableFixes = options.disableFixes || !supportsAutofix;
  1721. return this._verifyWithProcessor(
  1722. textOrSourceCode,
  1723. config,
  1724. { ...options, disableFixes, postprocess, preprocess },
  1725. configArray
  1726. );
  1727. }
  1728. return this._verifyWithoutProcessors(textOrSourceCode, config, options);
  1729. }
  1730. /**
  1731. * Verify a given code with a flat config.
  1732. * @param {string|SourceCode} textOrSourceCode The source code.
  1733. * @param {FlatConfigArray} configArray The config array.
  1734. * @param {VerifyOptions&ProcessorOptions} options The options.
  1735. * @param {boolean} [firstCall=false] Indicates if this is being called directly
  1736. * from verify(). (TODO: Remove once eslintrc is removed.)
  1737. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1738. */
  1739. _verifyWithFlatConfigArray(textOrSourceCode, configArray, options, firstCall = false) {
  1740. debug("With flat config: %s", options.filename);
  1741. // we need a filename to match configs against
  1742. const filename = options.filename || "__placeholder__.js";
  1743. // Store the config array in order to get plugin envs and rules later.
  1744. internalSlotsMap.get(this).lastConfigArray = configArray;
  1745. const config = configArray.getConfig(filename);
  1746. if (!config) {
  1747. return [
  1748. {
  1749. ruleId: null,
  1750. severity: 1,
  1751. message: `No matching configuration found for ${filename}.`,
  1752. line: 0,
  1753. column: 0,
  1754. nodeType: null
  1755. }
  1756. ];
  1757. }
  1758. // Verify.
  1759. if (config.processor) {
  1760. debug("Apply the processor: %o", config.processor);
  1761. const { preprocess, postprocess, supportsAutofix } = config.processor;
  1762. const disableFixes = options.disableFixes || !supportsAutofix;
  1763. return this._verifyWithFlatConfigArrayAndProcessor(
  1764. textOrSourceCode,
  1765. config,
  1766. { ...options, filename, disableFixes, postprocess, preprocess },
  1767. configArray
  1768. );
  1769. }
  1770. // check for options-based processing
  1771. if (firstCall && (options.preprocess || options.postprocess)) {
  1772. return this._verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options);
  1773. }
  1774. return this._verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, config, options);
  1775. }
  1776. /**
  1777. * Verify with a processor.
  1778. * @param {string|SourceCode} textOrSourceCode The source code.
  1779. * @param {ConfigData|ExtractedConfig} config The config array.
  1780. * @param {VerifyOptions&ProcessorOptions} options The options.
  1781. * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
  1782. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1783. */
  1784. _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
  1785. const slots = internalSlotsMap.get(this);
  1786. const filename = options.filename || "<input>";
  1787. const filenameToExpose = normalizeFilename(filename);
  1788. const physicalFilename = options.physicalFilename || filenameToExpose;
  1789. const text = ensureText(textOrSourceCode);
  1790. const file = new VFile(filenameToExpose, text, {
  1791. physicalPath: physicalFilename
  1792. });
  1793. const preprocess = options.preprocess || (rawText => [rawText]);
  1794. const postprocess = options.postprocess || (messagesList => messagesList.flat());
  1795. const processorService = new ProcessorService();
  1796. const preprocessResult = processorService.preprocessSync(file, {
  1797. processor: {
  1798. preprocess,
  1799. postprocess
  1800. }
  1801. });
  1802. if (!preprocessResult.ok) {
  1803. return preprocessResult.errors;
  1804. }
  1805. const filterCodeBlock =
  1806. options.filterCodeBlock ||
  1807. (blockFilePath => blockFilePath.endsWith(".js"));
  1808. const originalExtname = path.extname(filename);
  1809. const { files } = preprocessResult;
  1810. const messageLists = files.map(block => {
  1811. debug("A code block was found: %o", block.path ?? "(unnamed)");
  1812. // Keep the legacy behavior.
  1813. if (typeof block === "string") {
  1814. return this._verifyWithoutProcessors(block, config, options);
  1815. }
  1816. // Skip this block if filtered.
  1817. if (!filterCodeBlock(block.path, block.body)) {
  1818. debug("This code block was skipped.");
  1819. return [];
  1820. }
  1821. // Resolve configuration again if the file content or extension was changed.
  1822. if (configForRecursive && (text !== block.rawBody || path.extname(block.path) !== originalExtname)) {
  1823. debug("Resolving configuration again because the file content or extension was changed.");
  1824. return this._verifyWithConfigArray(
  1825. block.rawBody,
  1826. configForRecursive,
  1827. { ...options, filename: block.path, physicalFilename: block.physicalPath }
  1828. );
  1829. }
  1830. slots.lastSourceCode = null;
  1831. // Does lint.
  1832. return this.#eslintrcVerifyWithoutProcessors(
  1833. block,
  1834. config,
  1835. { ...options, filename: block.path, physicalFilename: block.physicalPath }
  1836. );
  1837. });
  1838. return processorService.postprocessSync(file, messageLists, {
  1839. processor: {
  1840. preprocess,
  1841. postprocess
  1842. }
  1843. });
  1844. }
  1845. /**
  1846. * Given a list of reported problems, distinguish problems between normal messages and suppressed messages.
  1847. * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages.
  1848. * @param {Array<LintMessage|SuppressedLintMessage>} problems A list of reported problems.
  1849. * @returns {LintMessage[]} A list of LintMessage.
  1850. */
  1851. _distinguishSuppressedMessages(problems) {
  1852. const messages = [];
  1853. const suppressedMessages = [];
  1854. const slots = internalSlotsMap.get(this);
  1855. for (const problem of problems) {
  1856. if (problem.suppressions) {
  1857. suppressedMessages.push(problem);
  1858. } else {
  1859. messages.push(problem);
  1860. }
  1861. }
  1862. slots.lastSuppressedMessages = suppressedMessages;
  1863. return messages;
  1864. }
  1865. /**
  1866. * Gets the SourceCode object representing the parsed source.
  1867. * @returns {SourceCode} The SourceCode object.
  1868. */
  1869. getSourceCode() {
  1870. return internalSlotsMap.get(this).lastSourceCode;
  1871. }
  1872. /**
  1873. * Gets the times spent on (parsing, fixing, linting) a file.
  1874. * @returns {LintTimes} The times.
  1875. */
  1876. getTimes() {
  1877. return internalSlotsMap.get(this).times ?? { passes: [] };
  1878. }
  1879. /**
  1880. * Gets the number of autofix passes that were made in the last run.
  1881. * @returns {number} The number of autofix passes.
  1882. */
  1883. getFixPassCount() {
  1884. return internalSlotsMap.get(this).fixPasses ?? 0;
  1885. }
  1886. /**
  1887. * Gets the list of SuppressedLintMessage produced in the last running.
  1888. * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage
  1889. */
  1890. getSuppressedMessages() {
  1891. return internalSlotsMap.get(this).lastSuppressedMessages;
  1892. }
  1893. /**
  1894. * Defines a new linting rule.
  1895. * @param {string} ruleId A unique rule identifier
  1896. * @param {Rule} rule A rule object
  1897. * @returns {void}
  1898. */
  1899. defineRule(ruleId, rule) {
  1900. assertEslintrcConfig(this);
  1901. internalSlotsMap.get(this).ruleMap.define(ruleId, rule);
  1902. }
  1903. /**
  1904. * Defines many new linting rules.
  1905. * @param {Record<string, Rule>} rulesToDefine map from unique rule identifier to rule
  1906. * @returns {void}
  1907. */
  1908. defineRules(rulesToDefine) {
  1909. assertEslintrcConfig(this);
  1910. Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
  1911. this.defineRule(ruleId, rulesToDefine[ruleId]);
  1912. });
  1913. }
  1914. /**
  1915. * Gets an object with all loaded rules.
  1916. * @returns {Map<string, Rule>} All loaded rules
  1917. */
  1918. getRules() {
  1919. assertEslintrcConfig(this);
  1920. const { lastConfigArray, ruleMap } = internalSlotsMap.get(this);
  1921. return new Map(function *() {
  1922. yield* ruleMap;
  1923. if (lastConfigArray) {
  1924. yield* lastConfigArray.pluginRules;
  1925. }
  1926. }());
  1927. }
  1928. /**
  1929. * Define a new parser module
  1930. * @param {string} parserId Name of the parser
  1931. * @param {Parser} parserModule The parser object
  1932. * @returns {void}
  1933. */
  1934. defineParser(parserId, parserModule) {
  1935. assertEslintrcConfig(this);
  1936. internalSlotsMap.get(this).parserMap.set(parserId, parserModule);
  1937. }
  1938. /**
  1939. * Performs multiple autofix passes over the text until as many fixes as possible
  1940. * have been applied.
  1941. * @param {string} text The source text to apply fixes to.
  1942. * @param {ConfigData|ConfigArray|FlatConfigArray} config The ESLint config object to use.
  1943. * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use.
  1944. * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the
  1945. * SourceCodeFixer.
  1946. */
  1947. verifyAndFix(text, config, options) {
  1948. let messages,
  1949. fixedResult,
  1950. fixed = false,
  1951. passNumber = 0,
  1952. currentText = text;
  1953. const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
  1954. const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
  1955. const stats = options?.stats;
  1956. /**
  1957. * This loop continues until one of the following is true:
  1958. *
  1959. * 1. No more fixes have been applied.
  1960. * 2. Ten passes have been made.
  1961. *
  1962. * That means anytime a fix is successfully applied, there will be another pass.
  1963. * Essentially, guaranteeing a minimum of two passes.
  1964. */
  1965. const slots = internalSlotsMap.get(this);
  1966. // Remove lint times from the last run.
  1967. if (stats) {
  1968. delete slots.times;
  1969. slots.fixPasses = 0;
  1970. }
  1971. do {
  1972. passNumber++;
  1973. let tTotal;
  1974. if (stats) {
  1975. tTotal = startTime();
  1976. }
  1977. debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
  1978. messages = this.verify(currentText, config, options);
  1979. debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
  1980. let t;
  1981. if (stats) {
  1982. t = startTime();
  1983. }
  1984. fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
  1985. if (stats) {
  1986. if (fixedResult.fixed) {
  1987. const time = endTime(t);
  1988. storeTime(time, { type: "fix" }, slots);
  1989. slots.fixPasses++;
  1990. } else {
  1991. storeTime(0, { type: "fix" }, slots);
  1992. }
  1993. }
  1994. /*
  1995. * stop if there are any syntax errors.
  1996. * 'fixedResult.output' is a empty string.
  1997. */
  1998. if (messages.length === 1 && messages[0].fatal) {
  1999. break;
  2000. }
  2001. // keep track if any fixes were ever applied - important for return value
  2002. fixed = fixed || fixedResult.fixed;
  2003. // update to use the fixed output instead of the original text
  2004. currentText = fixedResult.output;
  2005. if (stats) {
  2006. tTotal = endTime(tTotal);
  2007. const passIndex = slots.times.passes.length - 1;
  2008. slots.times.passes[passIndex].total = tTotal;
  2009. }
  2010. } while (
  2011. fixedResult.fixed &&
  2012. passNumber < MAX_AUTOFIX_PASSES
  2013. );
  2014. /*
  2015. * If the last result had fixes, we need to lint again to be sure we have
  2016. * the most up-to-date information.
  2017. */
  2018. if (fixedResult.fixed) {
  2019. let tTotal;
  2020. if (stats) {
  2021. tTotal = startTime();
  2022. }
  2023. fixedResult.messages = this.verify(currentText, config, options);
  2024. if (stats) {
  2025. storeTime(0, { type: "fix" }, slots);
  2026. slots.times.passes.at(-1).total = endTime(tTotal);
  2027. }
  2028. }
  2029. // ensure the last result properly reflects if fixes were done
  2030. fixedResult.fixed = fixed;
  2031. fixedResult.output = currentText;
  2032. return fixedResult;
  2033. }
  2034. }
  2035. module.exports = {
  2036. Linter,
  2037. /**
  2038. * Get the internal slots of a given Linter instance for tests.
  2039. * @param {Linter} instance The Linter instance to get.
  2040. * @returns {LinterInternalSlots} The internal slots.
  2041. */
  2042. getLinterInternalSlots(instance) {
  2043. return internalSlotsMap.get(instance);
  2044. }
  2045. };