linter.js 77 KB

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