linter.js 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004
  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("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. astUtils = require("../shared/ast-utils"),
  18. {
  19. Legacy: {
  20. ConfigOps,
  21. ConfigValidator,
  22. environments: BuiltInEnvironments
  23. }
  24. } = require("@eslint/eslintrc/universal"),
  25. Traverser = require("../shared/traverser"),
  26. { SourceCode } = require("../source-code"),
  27. CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
  28. applyDisableDirectives = require("./apply-disable-directives"),
  29. ConfigCommentParser = require("./config-comment-parser"),
  30. NodeEventGenerator = require("./node-event-generator"),
  31. createReportTranslator = require("./report-translator"),
  32. Rules = require("./rules"),
  33. createEmitter = require("./safe-emitter"),
  34. SourceCodeFixer = require("./source-code-fixer"),
  35. timing = require("./timing"),
  36. ruleReplacements = require("../../conf/replacements.json");
  37. const { getRuleFromConfig } = require("../config/flat-config-helpers");
  38. const { FlatConfigArray } = require("../config/flat-config-array");
  39. const debug = require("debug")("eslint:linter");
  40. const MAX_AUTOFIX_PASSES = 10;
  41. const DEFAULT_PARSER_NAME = "espree";
  42. const DEFAULT_ECMA_VERSION = 5;
  43. const commentParser = new ConfigCommentParser();
  44. const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
  45. const parserSymbol = Symbol.for("eslint.RuleTester.parser");
  46. const globals = require("../../conf/globals");
  47. //------------------------------------------------------------------------------
  48. // Typedefs
  49. //------------------------------------------------------------------------------
  50. /** @typedef {InstanceType<import("../cli-engine/config-array").ConfigArray>} ConfigArray */
  51. /** @typedef {InstanceType<import("../cli-engine/config-array").ExtractedConfig>} ExtractedConfig */
  52. /** @typedef {import("../shared/types").ConfigData} ConfigData */
  53. /** @typedef {import("../shared/types").Environment} Environment */
  54. /** @typedef {import("../shared/types").GlobalConf} GlobalConf */
  55. /** @typedef {import("../shared/types").LintMessage} LintMessage */
  56. /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
  57. /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
  58. /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
  59. /** @typedef {import("../shared/types").Processor} Processor */
  60. /** @typedef {import("../shared/types").Rule} Rule */
  61. /* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
  62. /**
  63. * @template T
  64. * @typedef {{ [P in keyof T]-?: T[P] }} Required
  65. */
  66. /* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
  67. /**
  68. * @typedef {Object} DisableDirective
  69. * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type Type of directive
  70. * @property {number} line The line number
  71. * @property {number} column The column number
  72. * @property {(string|null)} ruleId The rule ID
  73. * @property {string} justification The justification of directive
  74. */
  75. /**
  76. * The private data for `Linter` instance.
  77. * @typedef {Object} LinterInternalSlots
  78. * @property {ConfigArray|null} lastConfigArray The `ConfigArray` instance that the last `verify()` call used.
  79. * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used.
  80. * @property {SuppressedLintMessage[]} lastSuppressedMessages The `SuppressedLintMessage[]` instance that the last `verify()` call produced.
  81. * @property {Map<string, Parser>} parserMap The loaded parsers.
  82. * @property {Rules} ruleMap The loaded rules.
  83. */
  84. /**
  85. * @typedef {Object} VerifyOptions
  86. * @property {boolean} [allowInlineConfig] Allow/disallow inline comments' ability
  87. * to change config once it is set. Defaults to true if not supplied.
  88. * Useful if you want to validate JS without comments overriding rules.
  89. * @property {boolean} [disableFixes] if `true` then the linter doesn't make `fix`
  90. * properties into the lint result.
  91. * @property {string} [filename] the filename of the source code.
  92. * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
  93. * unused `eslint-disable` directives.
  94. */
  95. /**
  96. * @typedef {Object} ProcessorOptions
  97. * @property {(filename:string, text:string) => boolean} [filterCodeBlock] the
  98. * predicate function that selects adopt code blocks.
  99. * @property {Processor.postprocess} [postprocess] postprocessor for report
  100. * messages. If provided, this should accept an array of the message lists
  101. * for each code block returned from the preprocessor, apply a mapping to
  102. * the messages as appropriate, and return a one-dimensional array of
  103. * messages.
  104. * @property {Processor.preprocess} [preprocess] preprocessor for source text.
  105. * If provided, this should accept a string of source text, and return an
  106. * array of code blocks to lint.
  107. */
  108. /**
  109. * @typedef {Object} FixOptions
  110. * @property {boolean | ((message: LintMessage) => boolean)} [fix] Determines
  111. * whether fixes should be applied.
  112. */
  113. /**
  114. * @typedef {Object} InternalOptions
  115. * @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.
  116. * @property {"off" | "warn" | "error"} reportUnusedDisableDirectives (boolean values were normalized)
  117. */
  118. //------------------------------------------------------------------------------
  119. // Helpers
  120. //------------------------------------------------------------------------------
  121. /**
  122. * Determines if a given object is Espree.
  123. * @param {Object} parser The parser to check.
  124. * @returns {boolean} True if the parser is Espree or false if not.
  125. */
  126. function isEspree(parser) {
  127. return !!(parser === espree || parser[parserSymbol] === espree);
  128. }
  129. /**
  130. * Retrieves globals for the given ecmaVersion.
  131. * @param {number} ecmaVersion The version to retrieve globals for.
  132. * @returns {Object} The globals for the given ecmaVersion.
  133. */
  134. function getGlobalsForEcmaVersion(ecmaVersion) {
  135. switch (ecmaVersion) {
  136. case 3:
  137. return globals.es3;
  138. case 5:
  139. return globals.es5;
  140. default:
  141. if (ecmaVersion < 2015) {
  142. return globals[`es${ecmaVersion + 2009}`];
  143. }
  144. return globals[`es${ecmaVersion}`];
  145. }
  146. }
  147. /**
  148. * Ensures that variables representing built-in properties of the Global Object,
  149. * and any globals declared by special block comments, are present in the global
  150. * scope.
  151. * @param {Scope} globalScope The global scope.
  152. * @param {Object} configGlobals The globals declared in configuration
  153. * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
  154. * @returns {void}
  155. */
  156. function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) {
  157. // Define configured global variables.
  158. for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) {
  159. /*
  160. * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
  161. * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
  162. */
  163. const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]);
  164. const commentValue = enabledGlobals[id] && enabledGlobals[id].value;
  165. const value = commentValue || configValue;
  166. const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments;
  167. if (value === "off") {
  168. continue;
  169. }
  170. let variable = globalScope.set.get(id);
  171. if (!variable) {
  172. variable = new eslintScope.Variable(id, globalScope);
  173. globalScope.variables.push(variable);
  174. globalScope.set.set(id, variable);
  175. }
  176. variable.eslintImplicitGlobalSetting = configValue;
  177. variable.eslintExplicitGlobal = sourceComments !== void 0;
  178. variable.eslintExplicitGlobalComments = sourceComments;
  179. variable.writeable = (value === "writable");
  180. }
  181. // mark all exported variables as such
  182. Object.keys(exportedVariables).forEach(name => {
  183. const variable = globalScope.set.get(name);
  184. if (variable) {
  185. variable.eslintUsed = true;
  186. }
  187. });
  188. /*
  189. * "through" contains all references which definitions cannot be found.
  190. * Since we augment the global scope using configuration, we need to update
  191. * references and remove the ones that were added by configuration.
  192. */
  193. globalScope.through = globalScope.through.filter(reference => {
  194. const name = reference.identifier.name;
  195. const variable = globalScope.set.get(name);
  196. if (variable) {
  197. /*
  198. * Links the variable and the reference.
  199. * And this reference is removed from `Scope#through`.
  200. */
  201. reference.resolved = variable;
  202. variable.references.push(reference);
  203. return false;
  204. }
  205. return true;
  206. });
  207. }
  208. /**
  209. * creates a missing-rule message.
  210. * @param {string} ruleId the ruleId to create
  211. * @returns {string} created error message
  212. * @private
  213. */
  214. function createMissingRuleMessage(ruleId) {
  215. return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId)
  216. ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
  217. : `Definition for rule '${ruleId}' was not found.`;
  218. }
  219. /**
  220. * creates a linting problem
  221. * @param {Object} options to create linting error
  222. * @param {string} [options.ruleId] the ruleId to report
  223. * @param {Object} [options.loc] the loc to report
  224. * @param {string} [options.message] the error message to report
  225. * @param {string} [options.severity] the error message to report
  226. * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
  227. * @private
  228. */
  229. function createLintingProblem(options) {
  230. const {
  231. ruleId = null,
  232. loc = DEFAULT_ERROR_LOC,
  233. message = createMissingRuleMessage(options.ruleId),
  234. severity = 2
  235. } = options;
  236. return {
  237. ruleId,
  238. message,
  239. line: loc.start.line,
  240. column: loc.start.column + 1,
  241. endLine: loc.end.line,
  242. endColumn: loc.end.column + 1,
  243. severity,
  244. nodeType: null
  245. };
  246. }
  247. /**
  248. * Creates a collection of disable directives from a comment
  249. * @param {Object} options to create disable directives
  250. * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
  251. * @param {token} options.commentToken The Comment token
  252. * @param {string} options.value The value after the directive in the comment
  253. * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
  254. * @param {string} options.justification The justification of the directive
  255. * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules
  256. * @returns {Object} Directives and problems from the comment
  257. */
  258. function createDisableDirectives(options) {
  259. const { commentToken, type, value, justification, ruleMapper } = options;
  260. const ruleIds = Object.keys(commentParser.parseListConfig(value));
  261. const directiveRules = ruleIds.length ? ruleIds : [null];
  262. const result = {
  263. directives: [], // valid disable directives
  264. directiveProblems: [] // problems in directives
  265. };
  266. const parentComment = { commentToken, ruleIds };
  267. for (const ruleId of directiveRules) {
  268. // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
  269. if (ruleId === null || !!ruleMapper(ruleId)) {
  270. if (type === "disable-next-line") {
  271. result.directives.push({
  272. parentComment,
  273. type,
  274. line: commentToken.loc.end.line,
  275. column: commentToken.loc.end.column + 1,
  276. ruleId,
  277. justification
  278. });
  279. } else {
  280. result.directives.push({
  281. parentComment,
  282. type,
  283. line: commentToken.loc.start.line,
  284. column: commentToken.loc.start.column + 1,
  285. ruleId,
  286. justification
  287. });
  288. }
  289. } else {
  290. result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc }));
  291. }
  292. }
  293. return result;
  294. }
  295. /**
  296. * Extract the directive and the justification from a given directive comment and trim them.
  297. * @param {string} value The comment text to extract.
  298. * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
  299. */
  300. function extractDirectiveComment(value) {
  301. const match = /\s-{2,}\s/u.exec(value);
  302. if (!match) {
  303. return { directivePart: value.trim(), justificationPart: "" };
  304. }
  305. const directive = value.slice(0, match.index).trim();
  306. const justification = value.slice(match.index + match[0].length).trim();
  307. return { directivePart: directive, justificationPart: justification };
  308. }
  309. /**
  310. * Parses comments in file to extract file-specific config of rules, globals
  311. * and environments and merges them with global config; also code blocks
  312. * where reporting is disabled or enabled and merges them with reporting config.
  313. * @param {ASTNode} ast The top node of the AST.
  314. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
  315. * @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.
  316. * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
  317. * A collection of the directive comments that were found, along with any problems that occurred when parsing
  318. */
  319. function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
  320. const configuredRules = {};
  321. const enabledGlobals = Object.create(null);
  322. const exportedVariables = {};
  323. const problems = [];
  324. const disableDirectives = [];
  325. const validator = new ConfigValidator({
  326. builtInRules: Rules
  327. });
  328. ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
  329. const { directivePart, justificationPart } = extractDirectiveComment(comment.value);
  330. const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(directivePart);
  331. if (!match) {
  332. return;
  333. }
  334. const directiveText = match[1];
  335. const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
  336. if (comment.type === "Line" && !lineCommentSupported) {
  337. return;
  338. }
  339. if (warnInlineConfig) {
  340. const kind = comment.type === "Block" ? `/*${directiveText}*/` : `//${directiveText}`;
  341. problems.push(createLintingProblem({
  342. ruleId: null,
  343. message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
  344. loc: comment.loc,
  345. severity: 1
  346. }));
  347. return;
  348. }
  349. if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
  350. const message = `${directiveText} comment should not span multiple lines.`;
  351. problems.push(createLintingProblem({
  352. ruleId: null,
  353. message,
  354. loc: comment.loc
  355. }));
  356. return;
  357. }
  358. const directiveValue = directivePart.slice(match.index + directiveText.length);
  359. switch (directiveText) {
  360. case "eslint-disable":
  361. case "eslint-enable":
  362. case "eslint-disable-next-line":
  363. case "eslint-disable-line": {
  364. const directiveType = directiveText.slice("eslint-".length);
  365. const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
  366. const { directives, directiveProblems } = createDisableDirectives(options);
  367. disableDirectives.push(...directives);
  368. problems.push(...directiveProblems);
  369. break;
  370. }
  371. case "exported":
  372. Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
  373. break;
  374. case "globals":
  375. case "global":
  376. for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
  377. let normalizedValue;
  378. try {
  379. normalizedValue = ConfigOps.normalizeConfigGlobal(value);
  380. } catch (err) {
  381. problems.push(createLintingProblem({
  382. ruleId: null,
  383. loc: comment.loc,
  384. message: err.message
  385. }));
  386. continue;
  387. }
  388. if (enabledGlobals[id]) {
  389. enabledGlobals[id].comments.push(comment);
  390. enabledGlobals[id].value = normalizedValue;
  391. } else {
  392. enabledGlobals[id] = {
  393. comments: [comment],
  394. value: normalizedValue
  395. };
  396. }
  397. }
  398. break;
  399. case "eslint": {
  400. const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
  401. if (parseResult.success) {
  402. Object.keys(parseResult.config).forEach(name => {
  403. const rule = ruleMapper(name);
  404. const ruleValue = parseResult.config[name];
  405. if (!rule) {
  406. problems.push(createLintingProblem({ ruleId: name, loc: comment.loc }));
  407. return;
  408. }
  409. try {
  410. validator.validateRuleOptions(rule, name, ruleValue);
  411. } catch (err) {
  412. problems.push(createLintingProblem({
  413. ruleId: name,
  414. message: err.message,
  415. loc: comment.loc
  416. }));
  417. // do not apply the config, if found invalid options.
  418. return;
  419. }
  420. configuredRules[name] = ruleValue;
  421. });
  422. } else {
  423. problems.push(parseResult.error);
  424. }
  425. break;
  426. }
  427. // no default
  428. }
  429. });
  430. return {
  431. configuredRules,
  432. enabledGlobals,
  433. exportedVariables,
  434. problems,
  435. disableDirectives
  436. };
  437. }
  438. /**
  439. * Normalize ECMAScript version from the initial config
  440. * @param {Parser} parser The parser which uses this options.
  441. * @param {number} ecmaVersion ECMAScript version from the initial config
  442. * @returns {number} normalized ECMAScript version
  443. */
  444. function normalizeEcmaVersion(parser, ecmaVersion) {
  445. if (isEspree(parser)) {
  446. if (ecmaVersion === "latest") {
  447. return espree.latestEcmaVersion;
  448. }
  449. }
  450. /*
  451. * Calculate ECMAScript edition number from official year version starting with
  452. * ES2015, which corresponds with ES6 (or a difference of 2009).
  453. */
  454. return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
  455. }
  456. /**
  457. * Normalize ECMAScript version from the initial config into languageOptions (year)
  458. * format.
  459. * @param {any} [ecmaVersion] ECMAScript version from the initial config
  460. * @returns {number} normalized ECMAScript version
  461. */
  462. function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
  463. switch (ecmaVersion) {
  464. case 3:
  465. return 3;
  466. // void 0 = no ecmaVersion specified so use the default
  467. case 5:
  468. case void 0:
  469. return 5;
  470. default:
  471. if (typeof ecmaVersion === "number") {
  472. return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
  473. }
  474. }
  475. /*
  476. * We default to the latest supported ecmaVersion for everything else.
  477. * Remember, this is for languageOptions.ecmaVersion, which sets the version
  478. * that is used for a number of processes inside of ESLint. It's normally
  479. * safe to assume people want the latest unless otherwise specified.
  480. */
  481. return espree.latestEcmaVersion + 2009;
  482. }
  483. const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
  484. /**
  485. * Checks whether or not there is a comment which has "eslint-env *" in a given text.
  486. * @param {string} text A source code text to check.
  487. * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
  488. */
  489. function findEslintEnv(text) {
  490. let match, retv;
  491. eslintEnvPattern.lastIndex = 0;
  492. while ((match = eslintEnvPattern.exec(text)) !== null) {
  493. if (match[0].endsWith("*/")) {
  494. retv = Object.assign(
  495. retv || {},
  496. commentParser.parseListConfig(extractDirectiveComment(match[1]).directivePart)
  497. );
  498. }
  499. }
  500. return retv;
  501. }
  502. /**
  503. * Convert "/path/to/<text>" to "<text>".
  504. * `CLIEngine#executeOnText()` method gives "/path/to/<text>" if the filename
  505. * was omitted because `configArray.extractConfig()` requires an absolute path.
  506. * But the linter should pass `<text>` to `RuleContext#getFilename()` in that
  507. * case.
  508. * Also, code blocks can have their virtual filename. If the parent filename was
  509. * `<text>`, the virtual filename is `<text>/0_foo.js` or something like (i.e.,
  510. * it's not an absolute path).
  511. * @param {string} filename The filename to normalize.
  512. * @returns {string} The normalized filename.
  513. */
  514. function normalizeFilename(filename) {
  515. const parts = filename.split(path.sep);
  516. const index = parts.lastIndexOf("<text>");
  517. return index === -1 ? filename : parts.slice(index).join(path.sep);
  518. }
  519. /**
  520. * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
  521. * consistent shape.
  522. * @param {VerifyOptions} providedOptions Options
  523. * @param {ConfigData} config Config.
  524. * @returns {Required<VerifyOptions> & InternalOptions} Normalized options
  525. */
  526. function normalizeVerifyOptions(providedOptions, config) {
  527. const linterOptions = config.linterOptions || config;
  528. // .noInlineConfig for eslintrc, .linterOptions.noInlineConfig for flat
  529. const disableInlineConfig = linterOptions.noInlineConfig === true;
  530. const ignoreInlineConfig = providedOptions.allowInlineConfig === false;
  531. const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig
  532. ? ` (${config.configNameOfNoInlineConfig})`
  533. : "";
  534. let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives;
  535. if (typeof reportUnusedDisableDirectives === "boolean") {
  536. reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off";
  537. }
  538. if (typeof reportUnusedDisableDirectives !== "string") {
  539. reportUnusedDisableDirectives =
  540. linterOptions.reportUnusedDisableDirectives
  541. ? "warn" : "off";
  542. }
  543. return {
  544. filename: normalizeFilename(providedOptions.filename || "<input>"),
  545. allowInlineConfig: !ignoreInlineConfig,
  546. warnInlineConfig: disableInlineConfig && !ignoreInlineConfig
  547. ? `your config${configNameOfNoInlineConfig}`
  548. : null,
  549. reportUnusedDisableDirectives,
  550. disableFixes: Boolean(providedOptions.disableFixes)
  551. };
  552. }
  553. /**
  554. * Combines the provided parserOptions with the options from environments
  555. * @param {Parser} parser The parser which uses this options.
  556. * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
  557. * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  558. * @returns {ParserOptions} Resulting parser options after merge
  559. */
  560. function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
  561. const parserOptionsFromEnv = enabledEnvironments
  562. .filter(env => env.parserOptions)
  563. .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
  564. const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
  565. const isModule = mergedParserOptions.sourceType === "module";
  566. if (isModule) {
  567. /*
  568. * can't have global return inside of modules
  569. * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add)
  570. */
  571. mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
  572. }
  573. mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);
  574. return mergedParserOptions;
  575. }
  576. /**
  577. * Converts parserOptions to languageOptions for backwards compatibility with eslintrc.
  578. * @param {ConfigData} config Config object.
  579. * @param {Object} config.globals Global variable definitions.
  580. * @param {Parser} config.parser The parser to use.
  581. * @param {ParserOptions} config.parserOptions The parserOptions to use.
  582. * @returns {LanguageOptions} The languageOptions equivalent.
  583. */
  584. function createLanguageOptions({ globals: configuredGlobals, parser, parserOptions }) {
  585. const {
  586. ecmaVersion,
  587. sourceType
  588. } = parserOptions;
  589. return {
  590. globals: configuredGlobals,
  591. ecmaVersion: normalizeEcmaVersionForLanguageOptions(ecmaVersion),
  592. sourceType,
  593. parser,
  594. parserOptions
  595. };
  596. }
  597. /**
  598. * Combines the provided globals object with the globals from environments
  599. * @param {Record<string, GlobalConf>} providedGlobals The 'globals' key in a config
  600. * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  601. * @returns {Record<string, GlobalConf>} The resolved globals object
  602. */
  603. function resolveGlobals(providedGlobals, enabledEnvironments) {
  604. return Object.assign(
  605. {},
  606. ...enabledEnvironments.filter(env => env.globals).map(env => env.globals),
  607. providedGlobals
  608. );
  609. }
  610. /**
  611. * Strips Unicode BOM from a given text.
  612. * @param {string} text A text to strip.
  613. * @returns {string} The stripped text.
  614. */
  615. function stripUnicodeBOM(text) {
  616. /*
  617. * Check Unicode BOM.
  618. * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
  619. * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
  620. */
  621. if (text.charCodeAt(0) === 0xFEFF) {
  622. return text.slice(1);
  623. }
  624. return text;
  625. }
  626. /**
  627. * Get the options for a rule (not including severity), if any
  628. * @param {Array|number} ruleConfig rule configuration
  629. * @returns {Array} of rule options, empty Array if none
  630. */
  631. function getRuleOptions(ruleConfig) {
  632. if (Array.isArray(ruleConfig)) {
  633. return ruleConfig.slice(1);
  634. }
  635. return [];
  636. }
  637. /**
  638. * Analyze scope of the given AST.
  639. * @param {ASTNode} ast The `Program` node to analyze.
  640. * @param {LanguageOptions} languageOptions The parser options.
  641. * @param {Record<string, string[]>} visitorKeys The visitor keys.
  642. * @returns {ScopeManager} The analysis result.
  643. */
  644. function analyzeScope(ast, languageOptions, visitorKeys) {
  645. const parserOptions = languageOptions.parserOptions;
  646. const ecmaFeatures = parserOptions.ecmaFeatures || {};
  647. const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
  648. return eslintScope.analyze(ast, {
  649. ignoreEval: true,
  650. nodejsScope: ecmaFeatures.globalReturn,
  651. impliedStrict: ecmaFeatures.impliedStrict,
  652. ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
  653. sourceType: languageOptions.sourceType || "script",
  654. childVisitorKeys: visitorKeys || evk.KEYS,
  655. fallback: Traverser.getKeys
  656. });
  657. }
  658. /**
  659. * Parses text into an AST. Moved out here because the try-catch prevents
  660. * optimization of functions, so it's best to keep the try-catch as isolated
  661. * as possible
  662. * @param {string} text The text to parse.
  663. * @param {LanguageOptions} languageOptions Options to pass to the parser
  664. * @param {string} filePath The path to the file being parsed.
  665. * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
  666. * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
  667. * @private
  668. */
  669. function parse(text, languageOptions, filePath) {
  670. const textToParse = stripUnicodeBOM(text).replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
  671. const { ecmaVersion, sourceType, parser } = languageOptions;
  672. const parserOptions = Object.assign(
  673. { ecmaVersion, sourceType },
  674. languageOptions.parserOptions,
  675. {
  676. loc: true,
  677. range: true,
  678. raw: true,
  679. tokens: true,
  680. comment: true,
  681. eslintVisitorKeys: true,
  682. eslintScopeManager: true,
  683. filePath
  684. }
  685. );
  686. /*
  687. * Check for parsing errors first. If there's a parsing error, nothing
  688. * else can happen. However, a parsing error does not throw an error
  689. * from this method - it's just considered a fatal error message, a
  690. * problem that ESLint identified just like any other.
  691. */
  692. try {
  693. debug("Parsing:", filePath);
  694. const parseResult = (typeof parser.parseForESLint === "function")
  695. ? parser.parseForESLint(textToParse, parserOptions)
  696. : { ast: parser.parse(textToParse, parserOptions) };
  697. debug("Parsing successful:", filePath);
  698. const ast = parseResult.ast;
  699. const parserServices = parseResult.services || {};
  700. const visitorKeys = parseResult.visitorKeys || evk.KEYS;
  701. debug("Scope analysis:", filePath);
  702. const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys);
  703. debug("Scope analysis successful:", filePath);
  704. return {
  705. success: true,
  706. /*
  707. * Save all values that `parseForESLint()` returned.
  708. * If a `SourceCode` object is given as the first parameter instead of source code text,
  709. * linter skips the parsing process and reuses the source code object.
  710. * In that case, linter needs all the values that `parseForESLint()` returned.
  711. */
  712. sourceCode: new SourceCode({
  713. text,
  714. ast,
  715. parserServices,
  716. scopeManager,
  717. visitorKeys
  718. })
  719. };
  720. } catch (ex) {
  721. // If the message includes a leading line number, strip it:
  722. const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
  723. debug("%s\n%s", message, ex.stack);
  724. return {
  725. success: false,
  726. error: {
  727. ruleId: null,
  728. fatal: true,
  729. severity: 2,
  730. message,
  731. line: ex.lineNumber,
  732. column: ex.column
  733. }
  734. };
  735. }
  736. }
  737. /**
  738. * Gets the scope for the current node
  739. * @param {ScopeManager} scopeManager The scope manager for this AST
  740. * @param {ASTNode} currentNode The node to get the scope of
  741. * @returns {eslint-scope.Scope} The scope information for this node
  742. */
  743. function getScope(scopeManager, currentNode) {
  744. // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
  745. const inner = currentNode.type !== "Program";
  746. for (let node = currentNode; node; node = node.parent) {
  747. const scope = scopeManager.acquire(node, inner);
  748. if (scope) {
  749. if (scope.type === "function-expression-name") {
  750. return scope.childScopes[0];
  751. }
  752. return scope;
  753. }
  754. }
  755. return scopeManager.scopes[0];
  756. }
  757. /**
  758. * Marks a variable as used in the current scope
  759. * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function.
  760. * @param {ASTNode} currentNode The node currently being traversed
  761. * @param {LanguageOptions} languageOptions The options used to parse this text
  762. * @param {string} name The name of the variable that should be marked as used.
  763. * @returns {boolean} True if the variable was found and marked as used, false if not.
  764. */
  765. function markVariableAsUsed(scopeManager, currentNode, languageOptions, name) {
  766. const parserOptions = languageOptions.parserOptions;
  767. const sourceType = languageOptions.sourceType;
  768. const hasGlobalReturn =
  769. (parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn) ||
  770. sourceType === "commonjs";
  771. const specialScope = hasGlobalReturn || sourceType === "module";
  772. const currentScope = getScope(scopeManager, currentNode);
  773. // Special Node.js scope means we need to start one level deeper
  774. const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope;
  775. for (let scope = initialScope; scope; scope = scope.upper) {
  776. const variable = scope.variables.find(scopeVar => scopeVar.name === name);
  777. if (variable) {
  778. variable.eslintUsed = true;
  779. return true;
  780. }
  781. }
  782. return false;
  783. }
  784. /**
  785. * Runs a rule, and gets its listeners
  786. * @param {Rule} rule A normalized rule with a `create` method
  787. * @param {Context} ruleContext The context that should be passed to the rule
  788. * @throws {any} Any error during the rule's `create`
  789. * @returns {Object} A map of selector listeners provided by the rule
  790. */
  791. function createRuleListeners(rule, ruleContext) {
  792. try {
  793. return rule.create(ruleContext);
  794. } catch (ex) {
  795. ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`;
  796. throw ex;
  797. }
  798. }
  799. /**
  800. * Gets all the ancestors of a given node
  801. * @param {ASTNode} node The node
  802. * @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting
  803. * from the root node and going inwards to the parent node.
  804. */
  805. function getAncestors(node) {
  806. const ancestorsStartingAtParent = [];
  807. for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
  808. ancestorsStartingAtParent.push(ancestor);
  809. }
  810. return ancestorsStartingAtParent.reverse();
  811. }
  812. // methods that exist on SourceCode object
  813. const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
  814. getSource: "getText",
  815. getSourceLines: "getLines",
  816. getAllComments: "getAllComments",
  817. getNodeByRangeIndex: "getNodeByRangeIndex",
  818. getComments: "getComments",
  819. getCommentsBefore: "getCommentsBefore",
  820. getCommentsAfter: "getCommentsAfter",
  821. getCommentsInside: "getCommentsInside",
  822. getJSDocComment: "getJSDocComment",
  823. getFirstToken: "getFirstToken",
  824. getFirstTokens: "getFirstTokens",
  825. getLastToken: "getLastToken",
  826. getLastTokens: "getLastTokens",
  827. getTokenAfter: "getTokenAfter",
  828. getTokenBefore: "getTokenBefore",
  829. getTokenByRangeStart: "getTokenByRangeStart",
  830. getTokens: "getTokens",
  831. getTokensAfter: "getTokensAfter",
  832. getTokensBefore: "getTokensBefore",
  833. getTokensBetween: "getTokensBetween"
  834. };
  835. const BASE_TRAVERSAL_CONTEXT = Object.freeze(
  836. Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
  837. (contextInfo, methodName) =>
  838. Object.assign(contextInfo, {
  839. [methodName](...args) {
  840. return this.getSourceCode()[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args);
  841. }
  842. }),
  843. {}
  844. )
  845. );
  846. /**
  847. * Runs the given rules on the given SourceCode object
  848. * @param {SourceCode} sourceCode A SourceCode object for the given text
  849. * @param {Object} configuredRules The rules configuration
  850. * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
  851. * @param {string | undefined} parserName The name of the parser in the config
  852. * @param {LanguageOptions} languageOptions The options for parsing the code.
  853. * @param {Object} settings The settings that were enabled in the config
  854. * @param {string} filename The reported filename of the code
  855. * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
  856. * @param {string | undefined} cwd cwd of the cli
  857. * @param {string} physicalFilename The full path of the file on disk without any code block information
  858. * @returns {Problem[]} An array of reported problems
  859. */
  860. function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) {
  861. const emitter = createEmitter();
  862. const nodeQueue = [];
  863. let currentNode = sourceCode.ast;
  864. Traverser.traverse(sourceCode.ast, {
  865. enter(node, parent) {
  866. node.parent = parent;
  867. nodeQueue.push({ isEntering: true, node });
  868. },
  869. leave(node) {
  870. nodeQueue.push({ isEntering: false, node });
  871. },
  872. visitorKeys: sourceCode.visitorKeys
  873. });
  874. /*
  875. * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
  876. * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
  877. * properties once for each rule.
  878. */
  879. const sharedTraversalContext = Object.freeze(
  880. Object.assign(
  881. Object.create(BASE_TRAVERSAL_CONTEXT),
  882. {
  883. getAncestors: () => getAncestors(currentNode),
  884. getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
  885. getCwd: () => cwd,
  886. getFilename: () => filename,
  887. getPhysicalFilename: () => physicalFilename || filename,
  888. getScope: () => getScope(sourceCode.scopeManager, currentNode),
  889. getSourceCode: () => sourceCode,
  890. markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, languageOptions, name),
  891. parserOptions: {
  892. ...languageOptions.parserOptions
  893. },
  894. parserPath: parserName,
  895. languageOptions,
  896. parserServices: sourceCode.parserServices,
  897. settings
  898. }
  899. )
  900. );
  901. const lintingProblems = [];
  902. Object.keys(configuredRules).forEach(ruleId => {
  903. const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
  904. // not load disabled rules
  905. if (severity === 0) {
  906. return;
  907. }
  908. const rule = ruleMapper(ruleId);
  909. if (!rule) {
  910. lintingProblems.push(createLintingProblem({ ruleId }));
  911. return;
  912. }
  913. const messageIds = rule.meta && rule.meta.messages;
  914. let reportTranslator = null;
  915. const ruleContext = Object.freeze(
  916. Object.assign(
  917. Object.create(sharedTraversalContext),
  918. {
  919. id: ruleId,
  920. options: getRuleOptions(configuredRules[ruleId]),
  921. report(...args) {
  922. /*
  923. * Create a report translator lazily.
  924. * In a vast majority of cases, any given rule reports zero errors on a given
  925. * piece of code. Creating a translator lazily avoids the performance cost of
  926. * creating a new translator function for each rule that usually doesn't get
  927. * called.
  928. *
  929. * Using lazy report translators improves end-to-end performance by about 3%
  930. * with Node 8.4.0.
  931. */
  932. if (reportTranslator === null) {
  933. reportTranslator = createReportTranslator({
  934. ruleId,
  935. severity,
  936. sourceCode,
  937. messageIds,
  938. disableFixes
  939. });
  940. }
  941. const problem = reportTranslator(...args);
  942. if (problem.fix && !(rule.meta && rule.meta.fixable)) {
  943. throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
  944. }
  945. if (problem.suggestions && !(rule.meta && rule.meta.hasSuggestions === true)) {
  946. if (rule.meta && rule.meta.docs && typeof rule.meta.docs.suggestion !== "undefined") {
  947. // Encourage migration from the former property name.
  948. throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.");
  949. }
  950. throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
  951. }
  952. lintingProblems.push(problem);
  953. }
  954. }
  955. )
  956. );
  957. const ruleListeners = createRuleListeners(rule, ruleContext);
  958. /**
  959. * Include `ruleId` in error logs
  960. * @param {Function} ruleListener A rule method that listens for a node.
  961. * @returns {Function} ruleListener wrapped in error handler
  962. */
  963. function addRuleErrorHandler(ruleListener) {
  964. return function ruleErrorHandler(...listenerArgs) {
  965. try {
  966. return ruleListener(...listenerArgs);
  967. } catch (e) {
  968. e.ruleId = ruleId;
  969. throw e;
  970. }
  971. };
  972. }
  973. // add all the selectors from the rule as listeners
  974. Object.keys(ruleListeners).forEach(selector => {
  975. const ruleListener = timing.enabled
  976. ? timing.time(ruleId, ruleListeners[selector])
  977. : ruleListeners[selector];
  978. emitter.on(
  979. selector,
  980. addRuleErrorHandler(ruleListener)
  981. );
  982. });
  983. });
  984. // only run code path analyzer if the top level node is "Program", skip otherwise
  985. const eventGenerator = nodeQueue[0].node.type === "Program"
  986. ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
  987. : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
  988. nodeQueue.forEach(traversalInfo => {
  989. currentNode = traversalInfo.node;
  990. try {
  991. if (traversalInfo.isEntering) {
  992. eventGenerator.enterNode(currentNode);
  993. } else {
  994. eventGenerator.leaveNode(currentNode);
  995. }
  996. } catch (err) {
  997. err.currentNode = currentNode;
  998. throw err;
  999. }
  1000. });
  1001. return lintingProblems;
  1002. }
  1003. /**
  1004. * Ensure the source code to be a string.
  1005. * @param {string|SourceCode} textOrSourceCode The text or source code object.
  1006. * @returns {string} The source code text.
  1007. */
  1008. function ensureText(textOrSourceCode) {
  1009. if (typeof textOrSourceCode === "object") {
  1010. const { hasBOM, text } = textOrSourceCode;
  1011. const bom = hasBOM ? "\uFEFF" : "";
  1012. return bom + text;
  1013. }
  1014. return String(textOrSourceCode);
  1015. }
  1016. /**
  1017. * Get an environment.
  1018. * @param {LinterInternalSlots} slots The internal slots of Linter.
  1019. * @param {string} envId The environment ID to get.
  1020. * @returns {Environment|null} The environment.
  1021. */
  1022. function getEnv(slots, envId) {
  1023. return (
  1024. (slots.lastConfigArray && slots.lastConfigArray.pluginEnvironments.get(envId)) ||
  1025. BuiltInEnvironments.get(envId) ||
  1026. null
  1027. );
  1028. }
  1029. /**
  1030. * Get a rule.
  1031. * @param {LinterInternalSlots} slots The internal slots of Linter.
  1032. * @param {string} ruleId The rule ID to get.
  1033. * @returns {Rule|null} The rule.
  1034. */
  1035. function getRule(slots, ruleId) {
  1036. return (
  1037. (slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) ||
  1038. slots.ruleMap.get(ruleId)
  1039. );
  1040. }
  1041. /**
  1042. * Normalize the value of the cwd
  1043. * @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.
  1044. * @returns {string | undefined} normalized cwd
  1045. */
  1046. function normalizeCwd(cwd) {
  1047. if (cwd) {
  1048. return cwd;
  1049. }
  1050. if (typeof process === "object") {
  1051. return process.cwd();
  1052. }
  1053. // It's more explicit to assign the undefined
  1054. // eslint-disable-next-line no-undefined -- Consistently returning a value
  1055. return undefined;
  1056. }
  1057. /**
  1058. * The map to store private data.
  1059. * @type {WeakMap<Linter, LinterInternalSlots>}
  1060. */
  1061. const internalSlotsMap = new WeakMap();
  1062. /**
  1063. * Throws an error when the given linter is in flat config mode.
  1064. * @param {Linter} linter The linter to check.
  1065. * @returns {void}
  1066. * @throws {Error} If the linter is in flat config mode.
  1067. */
  1068. function assertEslintrcConfig(linter) {
  1069. const { configType } = internalSlotsMap.get(linter);
  1070. if (configType === "flat") {
  1071. throw new Error("This method cannot be used with flat config. Add your entries directly into the config array.");
  1072. }
  1073. }
  1074. //------------------------------------------------------------------------------
  1075. // Public Interface
  1076. //------------------------------------------------------------------------------
  1077. /**
  1078. * Object that is responsible for verifying JavaScript text
  1079. * @name Linter
  1080. */
  1081. class Linter {
  1082. /**
  1083. * Initialize the Linter.
  1084. * @param {Object} [config] the config object
  1085. * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
  1086. * @param {"flat"|"eslintrc"} [config.configType="eslintrc"] the type of config used.
  1087. */
  1088. constructor({ cwd, configType } = {}) {
  1089. internalSlotsMap.set(this, {
  1090. cwd: normalizeCwd(cwd),
  1091. lastConfigArray: null,
  1092. lastSourceCode: null,
  1093. lastSuppressedMessages: [],
  1094. configType, // TODO: Remove after flat config conversion
  1095. parserMap: new Map([["espree", espree]]),
  1096. ruleMap: new Rules()
  1097. });
  1098. this.version = pkg.version;
  1099. }
  1100. /**
  1101. * Getter for package version.
  1102. * @static
  1103. * @returns {string} The version from package.json.
  1104. */
  1105. static get version() {
  1106. return pkg.version;
  1107. }
  1108. /**
  1109. * Same as linter.verify, except without support for processors.
  1110. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  1111. * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
  1112. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
  1113. * @throws {Error} If during rule execution.
  1114. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
  1115. */
  1116. _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
  1117. const slots = internalSlotsMap.get(this);
  1118. const config = providedConfig || {};
  1119. const options = normalizeVerifyOptions(providedOptions, config);
  1120. let text;
  1121. // evaluate arguments
  1122. if (typeof textOrSourceCode === "string") {
  1123. slots.lastSourceCode = null;
  1124. text = textOrSourceCode;
  1125. } else {
  1126. slots.lastSourceCode = textOrSourceCode;
  1127. text = textOrSourceCode.text;
  1128. }
  1129. // Resolve parser.
  1130. let parserName = DEFAULT_PARSER_NAME;
  1131. let parser = espree;
  1132. if (typeof config.parser === "object" && config.parser !== null) {
  1133. parserName = config.parser.filePath;
  1134. parser = config.parser.definition;
  1135. } else if (typeof config.parser === "string") {
  1136. if (!slots.parserMap.has(config.parser)) {
  1137. return [{
  1138. ruleId: null,
  1139. fatal: true,
  1140. severity: 2,
  1141. message: `Configured parser '${config.parser}' was not found.`,
  1142. line: 0,
  1143. column: 0
  1144. }];
  1145. }
  1146. parserName = config.parser;
  1147. parser = slots.parserMap.get(config.parser);
  1148. }
  1149. // search and apply "eslint-env *".
  1150. const envInFile = options.allowInlineConfig && !options.warnInlineConfig
  1151. ? findEslintEnv(text)
  1152. : {};
  1153. const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
  1154. const enabledEnvs = Object.keys(resolvedEnvConfig)
  1155. .filter(envName => resolvedEnvConfig[envName])
  1156. .map(envName => getEnv(slots, envName))
  1157. .filter(env => env);
  1158. const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
  1159. const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
  1160. const settings = config.settings || {};
  1161. const languageOptions = createLanguageOptions({
  1162. globals: config.globals,
  1163. parser,
  1164. parserOptions
  1165. });
  1166. if (!slots.lastSourceCode) {
  1167. const parseResult = parse(
  1168. text,
  1169. languageOptions,
  1170. options.filename
  1171. );
  1172. if (!parseResult.success) {
  1173. return [parseResult.error];
  1174. }
  1175. slots.lastSourceCode = parseResult.sourceCode;
  1176. } else {
  1177. /*
  1178. * If the given source code object as the first argument does not have scopeManager, analyze the scope.
  1179. * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
  1180. */
  1181. if (!slots.lastSourceCode.scopeManager) {
  1182. slots.lastSourceCode = new SourceCode({
  1183. text: slots.lastSourceCode.text,
  1184. ast: slots.lastSourceCode.ast,
  1185. parserServices: slots.lastSourceCode.parserServices,
  1186. visitorKeys: slots.lastSourceCode.visitorKeys,
  1187. scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
  1188. });
  1189. }
  1190. }
  1191. const sourceCode = slots.lastSourceCode;
  1192. const commentDirectives = options.allowInlineConfig
  1193. ? getDirectiveComments(sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
  1194. : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
  1195. // augment global scope with declared global variables
  1196. addDeclaredGlobals(
  1197. sourceCode.scopeManager.scopes[0],
  1198. configuredGlobals,
  1199. { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
  1200. );
  1201. const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
  1202. let lintingProblems;
  1203. try {
  1204. lintingProblems = runRules(
  1205. sourceCode,
  1206. configuredRules,
  1207. ruleId => getRule(slots, ruleId),
  1208. parserName,
  1209. languageOptions,
  1210. settings,
  1211. options.filename,
  1212. options.disableFixes,
  1213. slots.cwd,
  1214. providedOptions.physicalFilename
  1215. );
  1216. } catch (err) {
  1217. err.message += `\nOccurred while linting ${options.filename}`;
  1218. debug("An error occurred while traversing");
  1219. debug("Filename:", options.filename);
  1220. if (err.currentNode) {
  1221. const { line } = err.currentNode.loc.start;
  1222. debug("Line:", line);
  1223. err.message += `:${line}`;
  1224. }
  1225. debug("Parser Options:", parserOptions);
  1226. debug("Parser Path:", parserName);
  1227. debug("Settings:", settings);
  1228. if (err.ruleId) {
  1229. err.message += `\nRule: "${err.ruleId}"`;
  1230. }
  1231. throw err;
  1232. }
  1233. return applyDisableDirectives({
  1234. directives: commentDirectives.disableDirectives,
  1235. disableFixes: options.disableFixes,
  1236. problems: lintingProblems
  1237. .concat(commentDirectives.problems)
  1238. .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
  1239. reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
  1240. });
  1241. }
  1242. /**
  1243. * Verifies the text against the rules specified by the second argument.
  1244. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  1245. * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything.
  1246. * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked.
  1247. * If this is not set, the filename will default to '<input>' in the rule context. If
  1248. * an object, then it has "filename", "allowInlineConfig", and some properties.
  1249. * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
  1250. */
  1251. verify(textOrSourceCode, config, filenameOrOptions) {
  1252. debug("Verify");
  1253. const { configType } = internalSlotsMap.get(this);
  1254. const options = typeof filenameOrOptions === "string"
  1255. ? { filename: filenameOrOptions }
  1256. : filenameOrOptions || {};
  1257. if (config) {
  1258. if (configType === "flat") {
  1259. /*
  1260. * Because of how Webpack packages up the files, we can't
  1261. * compare directly to `FlatConfigArray` using `instanceof`
  1262. * because it's not the same `FlatConfigArray` as in the tests.
  1263. * So, we work around it by assuming an array is, in fact, a
  1264. * `FlatConfigArray` if it has a `getConfig()` method.
  1265. */
  1266. let configArray = config;
  1267. if (!Array.isArray(config) || typeof config.getConfig !== "function") {
  1268. configArray = new FlatConfigArray(config);
  1269. configArray.normalizeSync();
  1270. }
  1271. return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true));
  1272. }
  1273. if (typeof config.extractConfig === "function") {
  1274. return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, config, options));
  1275. }
  1276. }
  1277. /*
  1278. * If we get to here, it means `config` is just an object rather
  1279. * than a config array so we can go right into linting.
  1280. */
  1281. /*
  1282. * `Linter` doesn't support `overrides` property in configuration.
  1283. * So we cannot apply multiple processors.
  1284. */
  1285. if (options.preprocess || options.postprocess) {
  1286. return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, config, options));
  1287. }
  1288. return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, config, options));
  1289. }
  1290. /**
  1291. * Verify with a processor.
  1292. * @param {string|SourceCode} textOrSourceCode The source code.
  1293. * @param {FlatConfig} config The config array.
  1294. * @param {VerifyOptions&ProcessorOptions} options The options.
  1295. * @param {FlatConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
  1296. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1297. */
  1298. _verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) {
  1299. const filename = options.filename || "<input>";
  1300. const filenameToExpose = normalizeFilename(filename);
  1301. const physicalFilename = options.physicalFilename || filenameToExpose;
  1302. const text = ensureText(textOrSourceCode);
  1303. const preprocess = options.preprocess || (rawText => [rawText]);
  1304. const postprocess = options.postprocess || (messagesList => messagesList.flat());
  1305. const filterCodeBlock =
  1306. options.filterCodeBlock ||
  1307. (blockFilename => blockFilename.endsWith(".js"));
  1308. const originalExtname = path.extname(filename);
  1309. const messageLists = preprocess(text, filenameToExpose).map((block, i) => {
  1310. debug("A code block was found: %o", block.filename || "(unnamed)");
  1311. // Keep the legacy behavior.
  1312. if (typeof block === "string") {
  1313. return this._verifyWithFlatConfigArrayAndWithoutProcessors(block, config, options);
  1314. }
  1315. const blockText = block.text;
  1316. const blockName = path.join(filename, `${i}_${block.filename}`);
  1317. // Skip this block if filtered.
  1318. if (!filterCodeBlock(blockName, blockText)) {
  1319. debug("This code block was skipped.");
  1320. return [];
  1321. }
  1322. // Resolve configuration again if the file content or extension was changed.
  1323. if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
  1324. debug("Resolving configuration again because the file content or extension was changed.");
  1325. return this._verifyWithFlatConfigArray(
  1326. blockText,
  1327. configForRecursive,
  1328. { ...options, filename: blockName, physicalFilename }
  1329. );
  1330. }
  1331. // Does lint.
  1332. return this._verifyWithFlatConfigArrayAndWithoutProcessors(
  1333. blockText,
  1334. config,
  1335. { ...options, filename: blockName, physicalFilename }
  1336. );
  1337. });
  1338. return postprocess(messageLists, filenameToExpose);
  1339. }
  1340. /**
  1341. * Same as linter.verify, except without support for processors.
  1342. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  1343. * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything.
  1344. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
  1345. * @throws {Error} If during rule execution.
  1346. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
  1347. */
  1348. _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
  1349. const slots = internalSlotsMap.get(this);
  1350. const config = providedConfig || {};
  1351. const options = normalizeVerifyOptions(providedOptions, config);
  1352. let text;
  1353. // evaluate arguments
  1354. if (typeof textOrSourceCode === "string") {
  1355. slots.lastSourceCode = null;
  1356. text = textOrSourceCode;
  1357. } else {
  1358. slots.lastSourceCode = textOrSourceCode;
  1359. text = textOrSourceCode.text;
  1360. }
  1361. const languageOptions = config.languageOptions;
  1362. languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
  1363. languageOptions.ecmaVersion
  1364. );
  1365. // add configured globals and language globals
  1366. const configuredGlobals = {
  1367. ...(getGlobalsForEcmaVersion(languageOptions.ecmaVersion)),
  1368. ...(languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0),
  1369. ...languageOptions.globals
  1370. };
  1371. // Espree expects this information to be passed in
  1372. if (isEspree(languageOptions.parser)) {
  1373. const parserOptions = languageOptions.parserOptions;
  1374. if (languageOptions.sourceType) {
  1375. parserOptions.sourceType = languageOptions.sourceType;
  1376. if (
  1377. parserOptions.sourceType === "module" &&
  1378. parserOptions.ecmaFeatures &&
  1379. parserOptions.ecmaFeatures.globalReturn
  1380. ) {
  1381. parserOptions.ecmaFeatures.globalReturn = false;
  1382. }
  1383. }
  1384. }
  1385. const settings = config.settings || {};
  1386. if (!slots.lastSourceCode) {
  1387. const parseResult = parse(
  1388. text,
  1389. languageOptions,
  1390. options.filename
  1391. );
  1392. if (!parseResult.success) {
  1393. return [parseResult.error];
  1394. }
  1395. slots.lastSourceCode = parseResult.sourceCode;
  1396. } else {
  1397. /*
  1398. * If the given source code object as the first argument does not have scopeManager, analyze the scope.
  1399. * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
  1400. */
  1401. if (!slots.lastSourceCode.scopeManager) {
  1402. slots.lastSourceCode = new SourceCode({
  1403. text: slots.lastSourceCode.text,
  1404. ast: slots.lastSourceCode.ast,
  1405. parserServices: slots.lastSourceCode.parserServices,
  1406. visitorKeys: slots.lastSourceCode.visitorKeys,
  1407. scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
  1408. });
  1409. }
  1410. }
  1411. const sourceCode = slots.lastSourceCode;
  1412. const commentDirectives = options.allowInlineConfig
  1413. ? getDirectiveComments(
  1414. sourceCode.ast,
  1415. ruleId => getRuleFromConfig(ruleId, config),
  1416. options.warnInlineConfig
  1417. )
  1418. : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
  1419. // augment global scope with declared global variables
  1420. addDeclaredGlobals(
  1421. sourceCode.scopeManager.scopes[0],
  1422. configuredGlobals,
  1423. { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
  1424. );
  1425. const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
  1426. let lintingProblems;
  1427. try {
  1428. lintingProblems = runRules(
  1429. sourceCode,
  1430. configuredRules,
  1431. ruleId => getRuleFromConfig(ruleId, config),
  1432. void 0,
  1433. languageOptions,
  1434. settings,
  1435. options.filename,
  1436. options.disableFixes,
  1437. slots.cwd,
  1438. providedOptions.physicalFilename
  1439. );
  1440. } catch (err) {
  1441. err.message += `\nOccurred while linting ${options.filename}`;
  1442. debug("An error occurred while traversing");
  1443. debug("Filename:", options.filename);
  1444. if (err.currentNode) {
  1445. const { line } = err.currentNode.loc.start;
  1446. debug("Line:", line);
  1447. err.message += `:${line}`;
  1448. }
  1449. debug("Parser Options:", languageOptions.parserOptions);
  1450. // debug("Parser Path:", parserName);
  1451. debug("Settings:", settings);
  1452. if (err.ruleId) {
  1453. err.message += `\nRule: "${err.ruleId}"`;
  1454. }
  1455. throw err;
  1456. }
  1457. return applyDisableDirectives({
  1458. directives: commentDirectives.disableDirectives,
  1459. disableFixes: options.disableFixes,
  1460. problems: lintingProblems
  1461. .concat(commentDirectives.problems)
  1462. .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
  1463. reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
  1464. });
  1465. }
  1466. /**
  1467. * Verify a given code with `ConfigArray`.
  1468. * @param {string|SourceCode} textOrSourceCode The source code.
  1469. * @param {ConfigArray} configArray The config array.
  1470. * @param {VerifyOptions&ProcessorOptions} options The options.
  1471. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1472. */
  1473. _verifyWithConfigArray(textOrSourceCode, configArray, options) {
  1474. debug("With ConfigArray: %s", options.filename);
  1475. // Store the config array in order to get plugin envs and rules later.
  1476. internalSlotsMap.get(this).lastConfigArray = configArray;
  1477. // Extract the final config for this file.
  1478. const config = configArray.extractConfig(options.filename);
  1479. const processor =
  1480. config.processor &&
  1481. configArray.pluginProcessors.get(config.processor);
  1482. // Verify.
  1483. if (processor) {
  1484. debug("Apply the processor: %o", config.processor);
  1485. const { preprocess, postprocess, supportsAutofix } = processor;
  1486. const disableFixes = options.disableFixes || !supportsAutofix;
  1487. return this._verifyWithProcessor(
  1488. textOrSourceCode,
  1489. config,
  1490. { ...options, disableFixes, postprocess, preprocess },
  1491. configArray
  1492. );
  1493. }
  1494. return this._verifyWithoutProcessors(textOrSourceCode, config, options);
  1495. }
  1496. /**
  1497. * Verify a given code with a flat config.
  1498. * @param {string|SourceCode} textOrSourceCode The source code.
  1499. * @param {FlatConfigArray} configArray The config array.
  1500. * @param {VerifyOptions&ProcessorOptions} options The options.
  1501. * @param {boolean} [firstCall=false] Indicates if this is being called directly
  1502. * from verify(). (TODO: Remove once eslintrc is removed.)
  1503. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1504. */
  1505. _verifyWithFlatConfigArray(textOrSourceCode, configArray, options, firstCall = false) {
  1506. debug("With flat config: %s", options.filename);
  1507. // we need a filename to match configs against
  1508. const filename = options.filename || "<input>";
  1509. // Store the config array in order to get plugin envs and rules later.
  1510. internalSlotsMap.get(this).lastConfigArray = configArray;
  1511. const config = configArray.getConfig(filename);
  1512. // Verify.
  1513. if (config.processor) {
  1514. debug("Apply the processor: %o", config.processor);
  1515. const { preprocess, postprocess, supportsAutofix } = config.processor;
  1516. const disableFixes = options.disableFixes || !supportsAutofix;
  1517. return this._verifyWithFlatConfigArrayAndProcessor(
  1518. textOrSourceCode,
  1519. config,
  1520. { ...options, filename, disableFixes, postprocess, preprocess },
  1521. configArray
  1522. );
  1523. }
  1524. // check for options-based processing
  1525. if (firstCall && (options.preprocess || options.postprocess)) {
  1526. return this._verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options);
  1527. }
  1528. return this._verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, config, options);
  1529. }
  1530. /**
  1531. * Verify with a processor.
  1532. * @param {string|SourceCode} textOrSourceCode The source code.
  1533. * @param {ConfigData|ExtractedConfig} config The config array.
  1534. * @param {VerifyOptions&ProcessorOptions} options The options.
  1535. * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
  1536. * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
  1537. */
  1538. _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
  1539. const filename = options.filename || "<input>";
  1540. const filenameToExpose = normalizeFilename(filename);
  1541. const physicalFilename = options.physicalFilename || filenameToExpose;
  1542. const text = ensureText(textOrSourceCode);
  1543. const preprocess = options.preprocess || (rawText => [rawText]);
  1544. const postprocess = options.postprocess || (messagesList => messagesList.flat());
  1545. const filterCodeBlock =
  1546. options.filterCodeBlock ||
  1547. (blockFilename => blockFilename.endsWith(".js"));
  1548. const originalExtname = path.extname(filename);
  1549. const messageLists = preprocess(text, filenameToExpose).map((block, i) => {
  1550. debug("A code block was found: %o", block.filename || "(unnamed)");
  1551. // Keep the legacy behavior.
  1552. if (typeof block === "string") {
  1553. return this._verifyWithoutProcessors(block, config, options);
  1554. }
  1555. const blockText = block.text;
  1556. const blockName = path.join(filename, `${i}_${block.filename}`);
  1557. // Skip this block if filtered.
  1558. if (!filterCodeBlock(blockName, blockText)) {
  1559. debug("This code block was skipped.");
  1560. return [];
  1561. }
  1562. // Resolve configuration again if the file content or extension was changed.
  1563. if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
  1564. debug("Resolving configuration again because the file content or extension was changed.");
  1565. return this._verifyWithConfigArray(
  1566. blockText,
  1567. configForRecursive,
  1568. { ...options, filename: blockName, physicalFilename }
  1569. );
  1570. }
  1571. // Does lint.
  1572. return this._verifyWithoutProcessors(
  1573. blockText,
  1574. config,
  1575. { ...options, filename: blockName, physicalFilename }
  1576. );
  1577. });
  1578. return postprocess(messageLists, filenameToExpose);
  1579. }
  1580. /**
  1581. * Given a list of reported problems, distinguish problems between normal messages and suppressed messages.
  1582. * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages.
  1583. * @param {Problem[]} problems A list of reported problems.
  1584. * @returns {LintMessage[]} A list of LintMessage.
  1585. */
  1586. _distinguishSuppressedMessages(problems) {
  1587. const messages = [];
  1588. const suppressedMessages = [];
  1589. const slots = internalSlotsMap.get(this);
  1590. for (const problem of problems) {
  1591. if (problem.suppressions) {
  1592. suppressedMessages.push(problem);
  1593. } else {
  1594. messages.push(problem);
  1595. }
  1596. }
  1597. slots.lastSuppressedMessages = suppressedMessages;
  1598. return messages;
  1599. }
  1600. /**
  1601. * Gets the SourceCode object representing the parsed source.
  1602. * @returns {SourceCode} The SourceCode object.
  1603. */
  1604. getSourceCode() {
  1605. return internalSlotsMap.get(this).lastSourceCode;
  1606. }
  1607. /**
  1608. * Gets the list of SuppressedLintMessage produced in the last running.
  1609. * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage
  1610. */
  1611. getSuppressedMessages() {
  1612. return internalSlotsMap.get(this).lastSuppressedMessages;
  1613. }
  1614. /**
  1615. * Defines a new linting rule.
  1616. * @param {string} ruleId A unique rule identifier
  1617. * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers
  1618. * @returns {void}
  1619. */
  1620. defineRule(ruleId, ruleModule) {
  1621. assertEslintrcConfig(this);
  1622. internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule);
  1623. }
  1624. /**
  1625. * Defines many new linting rules.
  1626. * @param {Record<string, Function | Rule>} rulesToDefine map from unique rule identifier to rule
  1627. * @returns {void}
  1628. */
  1629. defineRules(rulesToDefine) {
  1630. assertEslintrcConfig(this);
  1631. Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
  1632. this.defineRule(ruleId, rulesToDefine[ruleId]);
  1633. });
  1634. }
  1635. /**
  1636. * Gets an object with all loaded rules.
  1637. * @returns {Map<string, Rule>} All loaded rules
  1638. */
  1639. getRules() {
  1640. assertEslintrcConfig(this);
  1641. const { lastConfigArray, ruleMap } = internalSlotsMap.get(this);
  1642. return new Map(function *() {
  1643. yield* ruleMap;
  1644. if (lastConfigArray) {
  1645. yield* lastConfigArray.pluginRules;
  1646. }
  1647. }());
  1648. }
  1649. /**
  1650. * Define a new parser module
  1651. * @param {string} parserId Name of the parser
  1652. * @param {Parser} parserModule The parser object
  1653. * @returns {void}
  1654. */
  1655. defineParser(parserId, parserModule) {
  1656. assertEslintrcConfig(this);
  1657. internalSlotsMap.get(this).parserMap.set(parserId, parserModule);
  1658. }
  1659. /**
  1660. * Performs multiple autofix passes over the text until as many fixes as possible
  1661. * have been applied.
  1662. * @param {string} text The source text to apply fixes to.
  1663. * @param {ConfigData|ConfigArray|FlatConfigArray} config The ESLint config object to use.
  1664. * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use.
  1665. * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the
  1666. * SourceCodeFixer.
  1667. */
  1668. verifyAndFix(text, config, options) {
  1669. let messages = [],
  1670. fixedResult,
  1671. fixed = false,
  1672. passNumber = 0,
  1673. currentText = text;
  1674. const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
  1675. const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
  1676. /**
  1677. * This loop continues until one of the following is true:
  1678. *
  1679. * 1. No more fixes have been applied.
  1680. * 2. Ten passes have been made.
  1681. *
  1682. * That means anytime a fix is successfully applied, there will be another pass.
  1683. * Essentially, guaranteeing a minimum of two passes.
  1684. */
  1685. do {
  1686. passNumber++;
  1687. debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
  1688. messages = this.verify(currentText, config, options);
  1689. debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
  1690. fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
  1691. /*
  1692. * stop if there are any syntax errors.
  1693. * 'fixedResult.output' is a empty string.
  1694. */
  1695. if (messages.length === 1 && messages[0].fatal) {
  1696. break;
  1697. }
  1698. // keep track if any fixes were ever applied - important for return value
  1699. fixed = fixed || fixedResult.fixed;
  1700. // update to use the fixed output instead of the original text
  1701. currentText = fixedResult.output;
  1702. } while (
  1703. fixedResult.fixed &&
  1704. passNumber < MAX_AUTOFIX_PASSES
  1705. );
  1706. /*
  1707. * If the last result had fixes, we need to lint again to be sure we have
  1708. * the most up-to-date information.
  1709. */
  1710. if (fixedResult.fixed) {
  1711. fixedResult.messages = this.verify(currentText, config, options);
  1712. }
  1713. // ensure the last result properly reflects if fixes were done
  1714. fixedResult.fixed = fixed;
  1715. fixedResult.output = currentText;
  1716. return fixedResult;
  1717. }
  1718. }
  1719. module.exports = {
  1720. Linter,
  1721. /**
  1722. * Get the internal slots of a given Linter instance for tests.
  1723. * @param {Linter} instance The Linter instance to get.
  1724. * @returns {LinterInternalSlots} The internal slots.
  1725. */
  1726. getLinterInternalSlots(instance) {
  1727. return internalSlotsMap.get(instance);
  1728. }
  1729. };