Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

linter.js 55KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479
  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. BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
  17. pkg = require("../../package.json"),
  18. astUtils = require("../shared/ast-utils"),
  19. ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
  20. ConfigValidator = require("@eslint/eslintrc/lib/shared/config-validator"),
  21. Traverser = require("../shared/traverser"),
  22. { SourceCode } = require("../source-code"),
  23. CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
  24. applyDisableDirectives = require("./apply-disable-directives"),
  25. ConfigCommentParser = require("./config-comment-parser"),
  26. NodeEventGenerator = require("./node-event-generator"),
  27. createReportTranslator = require("./report-translator"),
  28. Rules = require("./rules"),
  29. createEmitter = require("./safe-emitter"),
  30. SourceCodeFixer = require("./source-code-fixer"),
  31. timing = require("./timing"),
  32. ruleReplacements = require("../../conf/replacements.json");
  33. const debug = require("debug")("eslint:linter");
  34. const MAX_AUTOFIX_PASSES = 10;
  35. const DEFAULT_PARSER_NAME = "espree";
  36. const DEFAULT_ECMA_VERSION = 5;
  37. const commentParser = new ConfigCommentParser();
  38. const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
  39. const parserSymbol = Symbol.for("eslint.RuleTester.parser");
  40. //------------------------------------------------------------------------------
  41. // Typedefs
  42. //------------------------------------------------------------------------------
  43. /** @typedef {InstanceType<import("../cli-engine/config-array")["ConfigArray"]>} ConfigArray */
  44. /** @typedef {InstanceType<import("../cli-engine/config-array")["ExtractedConfig"]>} ExtractedConfig */
  45. /** @typedef {import("../shared/types").ConfigData} ConfigData */
  46. /** @typedef {import("../shared/types").Environment} Environment */
  47. /** @typedef {import("../shared/types").GlobalConf} GlobalConf */
  48. /** @typedef {import("../shared/types").LintMessage} LintMessage */
  49. /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
  50. /** @typedef {import("../shared/types").Processor} Processor */
  51. /** @typedef {import("../shared/types").Rule} Rule */
  52. /**
  53. * @template T
  54. * @typedef {{ [P in keyof T]-?: T[P] }} Required
  55. */
  56. /**
  57. * @typedef {Object} DisableDirective
  58. * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type
  59. * @property {number} line
  60. * @property {number} column
  61. * @property {(string|null)} ruleId
  62. */
  63. /**
  64. * The private data for `Linter` instance.
  65. * @typedef {Object} LinterInternalSlots
  66. * @property {ConfigArray|null} lastConfigArray The `ConfigArray` instance that the last `verify()` call used.
  67. * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used.
  68. * @property {Map<string, Parser>} parserMap The loaded parsers.
  69. * @property {Rules} ruleMap The loaded rules.
  70. */
  71. /**
  72. * @typedef {Object} VerifyOptions
  73. * @property {boolean} [allowInlineConfig] Allow/disallow inline comments' ability
  74. * to change config once it is set. Defaults to true if not supplied.
  75. * Useful if you want to validate JS without comments overriding rules.
  76. * @property {boolean} [disableFixes] if `true` then the linter doesn't make `fix`
  77. * properties into the lint result.
  78. * @property {string} [filename] the filename of the source code.
  79. * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
  80. * unused `eslint-disable` directives.
  81. */
  82. /**
  83. * @typedef {Object} ProcessorOptions
  84. * @property {(filename:string, text:string) => boolean} [filterCodeBlock] the
  85. * predicate function that selects adopt code blocks.
  86. * @property {Processor["postprocess"]} [postprocess] postprocessor for report
  87. * messages. If provided, this should accept an array of the message lists
  88. * for each code block returned from the preprocessor, apply a mapping to
  89. * the messages as appropriate, and return a one-dimensional array of
  90. * messages.
  91. * @property {Processor["preprocess"]} [preprocess] preprocessor for source text.
  92. * If provided, this should accept a string of source text, and return an
  93. * array of code blocks to lint.
  94. */
  95. /**
  96. * @typedef {Object} FixOptions
  97. * @property {boolean | ((message: LintMessage) => boolean)} [fix] Determines
  98. * whether fixes should be applied.
  99. */
  100. /**
  101. * @typedef {Object} InternalOptions
  102. * @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.
  103. * @property {"off" | "warn" | "error"} reportUnusedDisableDirectives (boolean values were normalized)
  104. */
  105. //------------------------------------------------------------------------------
  106. // Helpers
  107. //------------------------------------------------------------------------------
  108. /**
  109. * Ensures that variables representing built-in properties of the Global Object,
  110. * and any globals declared by special block comments, are present in the global
  111. * scope.
  112. * @param {Scope} globalScope The global scope.
  113. * @param {Object} configGlobals The globals declared in configuration
  114. * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
  115. * @returns {void}
  116. */
  117. function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) {
  118. // Define configured global variables.
  119. for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) {
  120. /*
  121. * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
  122. * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
  123. */
  124. const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]);
  125. const commentValue = enabledGlobals[id] && enabledGlobals[id].value;
  126. const value = commentValue || configValue;
  127. const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments;
  128. if (value === "off") {
  129. continue;
  130. }
  131. let variable = globalScope.set.get(id);
  132. if (!variable) {
  133. variable = new eslintScope.Variable(id, globalScope);
  134. globalScope.variables.push(variable);
  135. globalScope.set.set(id, variable);
  136. }
  137. variable.eslintImplicitGlobalSetting = configValue;
  138. variable.eslintExplicitGlobal = sourceComments !== void 0;
  139. variable.eslintExplicitGlobalComments = sourceComments;
  140. variable.writeable = (value === "writable");
  141. }
  142. // mark all exported variables as such
  143. Object.keys(exportedVariables).forEach(name => {
  144. const variable = globalScope.set.get(name);
  145. if (variable) {
  146. variable.eslintUsed = true;
  147. }
  148. });
  149. /*
  150. * "through" contains all references which definitions cannot be found.
  151. * Since we augment the global scope using configuration, we need to update
  152. * references and remove the ones that were added by configuration.
  153. */
  154. globalScope.through = globalScope.through.filter(reference => {
  155. const name = reference.identifier.name;
  156. const variable = globalScope.set.get(name);
  157. if (variable) {
  158. /*
  159. * Links the variable and the reference.
  160. * And this reference is removed from `Scope#through`.
  161. */
  162. reference.resolved = variable;
  163. variable.references.push(reference);
  164. return false;
  165. }
  166. return true;
  167. });
  168. }
  169. /**
  170. * creates a missing-rule message.
  171. * @param {string} ruleId the ruleId to create
  172. * @returns {string} created error message
  173. * @private
  174. */
  175. function createMissingRuleMessage(ruleId) {
  176. return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId)
  177. ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
  178. : `Definition for rule '${ruleId}' was not found.`;
  179. }
  180. /**
  181. * creates a linting problem
  182. * @param {Object} options to create linting error
  183. * @param {string} [options.ruleId] the ruleId to report
  184. * @param {Object} [options.loc] the loc to report
  185. * @param {string} [options.message] the error message to report
  186. * @param {string} [options.severity] the error message to report
  187. * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
  188. * @private
  189. */
  190. function createLintingProblem(options) {
  191. const {
  192. ruleId = null,
  193. loc = DEFAULT_ERROR_LOC,
  194. message = createMissingRuleMessage(options.ruleId),
  195. severity = 2
  196. } = options;
  197. return {
  198. ruleId,
  199. message,
  200. line: loc.start.line,
  201. column: loc.start.column + 1,
  202. endLine: loc.end.line,
  203. endColumn: loc.end.column + 1,
  204. severity,
  205. nodeType: null
  206. };
  207. }
  208. /**
  209. * Creates a collection of disable directives from a comment
  210. * @param {Object} options to create disable directives
  211. * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
  212. * @param {{line: number, column: number}} options.loc The 0-based location of the comment token
  213. * @param {string} options.value The value after the directive in the comment
  214. * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
  215. * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules
  216. * @returns {Object} Directives and problems from the comment
  217. */
  218. function createDisableDirectives(options) {
  219. const { type, loc, value, ruleMapper } = options;
  220. const ruleIds = Object.keys(commentParser.parseListConfig(value));
  221. const directiveRules = ruleIds.length ? ruleIds : [null];
  222. const result = {
  223. directives: [], // valid disable directives
  224. directiveProblems: [] // problems in directives
  225. };
  226. for (const ruleId of directiveRules) {
  227. // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
  228. if (ruleId === null || ruleMapper(ruleId) !== null) {
  229. result.directives.push({ type, line: loc.start.line, column: loc.start.column + 1, ruleId });
  230. } else {
  231. result.directiveProblems.push(createLintingProblem({ ruleId, loc }));
  232. }
  233. }
  234. return result;
  235. }
  236. /**
  237. * Remove the ignored part from a given directive comment and trim it.
  238. * @param {string} value The comment text to strip.
  239. * @returns {string} The stripped text.
  240. */
  241. function stripDirectiveComment(value) {
  242. return value.split(/\s-{2,}\s/u)[0].trim();
  243. }
  244. /**
  245. * Parses comments in file to extract file-specific config of rules, globals
  246. * and environments and merges them with global config; also code blocks
  247. * where reporting is disabled or enabled and merges them with reporting config.
  248. * @param {string} filename The file being checked.
  249. * @param {ASTNode} ast The top node of the AST.
  250. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
  251. * @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.
  252. * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
  253. * A collection of the directive comments that were found, along with any problems that occurred when parsing
  254. */
  255. function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
  256. const configuredRules = {};
  257. const enabledGlobals = Object.create(null);
  258. const exportedVariables = {};
  259. const problems = [];
  260. const disableDirectives = [];
  261. const validator = new ConfigValidator({
  262. builtInRules: Rules
  263. });
  264. ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
  265. const trimmedCommentText = stripDirectiveComment(comment.value);
  266. const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText);
  267. if (!match) {
  268. return;
  269. }
  270. const directiveText = match[1];
  271. const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
  272. if (comment.type === "Line" && !lineCommentSupported) {
  273. return;
  274. }
  275. if (warnInlineConfig) {
  276. const kind = comment.type === "Block" ? `/*${directiveText}*/` : `//${directiveText}`;
  277. problems.push(createLintingProblem({
  278. ruleId: null,
  279. message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
  280. loc: comment.loc,
  281. severity: 1
  282. }));
  283. return;
  284. }
  285. if (lineCommentSupported && comment.loc.start.line !== comment.loc.end.line) {
  286. const message = `${directiveText} comment should not span multiple lines.`;
  287. problems.push(createLintingProblem({
  288. ruleId: null,
  289. message,
  290. loc: comment.loc
  291. }));
  292. return;
  293. }
  294. const directiveValue = trimmedCommentText.slice(match.index + directiveText.length);
  295. switch (directiveText) {
  296. case "eslint-disable":
  297. case "eslint-enable":
  298. case "eslint-disable-next-line":
  299. case "eslint-disable-line": {
  300. const directiveType = directiveText.slice("eslint-".length);
  301. const options = { type: directiveType, loc: comment.loc, value: directiveValue, ruleMapper };
  302. const { directives, directiveProblems } = createDisableDirectives(options);
  303. disableDirectives.push(...directives);
  304. problems.push(...directiveProblems);
  305. break;
  306. }
  307. case "exported":
  308. Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
  309. break;
  310. case "globals":
  311. case "global":
  312. for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
  313. let normalizedValue;
  314. try {
  315. normalizedValue = ConfigOps.normalizeConfigGlobal(value);
  316. } catch (err) {
  317. problems.push(createLintingProblem({
  318. ruleId: null,
  319. loc: comment.loc,
  320. message: err.message
  321. }));
  322. continue;
  323. }
  324. if (enabledGlobals[id]) {
  325. enabledGlobals[id].comments.push(comment);
  326. enabledGlobals[id].value = normalizedValue;
  327. } else {
  328. enabledGlobals[id] = {
  329. comments: [comment],
  330. value: normalizedValue
  331. };
  332. }
  333. }
  334. break;
  335. case "eslint": {
  336. const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
  337. if (parseResult.success) {
  338. Object.keys(parseResult.config).forEach(name => {
  339. const rule = ruleMapper(name);
  340. const ruleValue = parseResult.config[name];
  341. if (rule === null) {
  342. problems.push(createLintingProblem({ ruleId: name, loc: comment.loc }));
  343. return;
  344. }
  345. try {
  346. validator.validateRuleOptions(rule, name, ruleValue);
  347. } catch (err) {
  348. problems.push(createLintingProblem({
  349. ruleId: name,
  350. message: err.message,
  351. loc: comment.loc
  352. }));
  353. // do not apply the config, if found invalid options.
  354. return;
  355. }
  356. configuredRules[name] = ruleValue;
  357. });
  358. } else {
  359. problems.push(parseResult.error);
  360. }
  361. break;
  362. }
  363. // no default
  364. }
  365. });
  366. return {
  367. configuredRules,
  368. enabledGlobals,
  369. exportedVariables,
  370. problems,
  371. disableDirectives
  372. };
  373. }
  374. /**
  375. * Normalize ECMAScript version from the initial config
  376. * @param {Parser} parser The parser which uses this options.
  377. * @param {number} ecmaVersion ECMAScript version from the initial config
  378. * @returns {number} normalized ECMAScript version
  379. */
  380. function normalizeEcmaVersion(parser, ecmaVersion) {
  381. if ((parser[parserSymbol] || parser) === espree) {
  382. if (ecmaVersion === "latest") {
  383. return espree.latestEcmaVersion;
  384. }
  385. }
  386. /*
  387. * Calculate ECMAScript edition number from official year version starting with
  388. * ES2015, which corresponds with ES6 (or a difference of 2009).
  389. */
  390. return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
  391. }
  392. const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gsu;
  393. /**
  394. * Checks whether or not there is a comment which has "eslint-env *" in a given text.
  395. * @param {string} text A source code text to check.
  396. * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
  397. */
  398. function findEslintEnv(text) {
  399. let match, retv;
  400. eslintEnvPattern.lastIndex = 0;
  401. while ((match = eslintEnvPattern.exec(text)) !== null) {
  402. retv = Object.assign(
  403. retv || {},
  404. commentParser.parseListConfig(stripDirectiveComment(match[1]))
  405. );
  406. }
  407. return retv;
  408. }
  409. /**
  410. * Convert "/path/to/<text>" to "<text>".
  411. * `CLIEngine#executeOnText()` method gives "/path/to/<text>" if the filename
  412. * was omitted because `configArray.extractConfig()` requires an absolute path.
  413. * But the linter should pass `<text>` to `RuleContext#getFilename()` in that
  414. * case.
  415. * Also, code blocks can have their virtual filename. If the parent filename was
  416. * `<text>`, the virtual filename is `<text>/0_foo.js` or something like (i.e.,
  417. * it's not an absolute path).
  418. * @param {string} filename The filename to normalize.
  419. * @returns {string} The normalized filename.
  420. */
  421. function normalizeFilename(filename) {
  422. const parts = filename.split(path.sep);
  423. const index = parts.lastIndexOf("<text>");
  424. return index === -1 ? filename : parts.slice(index).join(path.sep);
  425. }
  426. /**
  427. * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
  428. * consistent shape.
  429. * @param {VerifyOptions} providedOptions Options
  430. * @param {ConfigData} config Config.
  431. * @returns {Required<VerifyOptions> & InternalOptions} Normalized options
  432. */
  433. function normalizeVerifyOptions(providedOptions, config) {
  434. const disableInlineConfig = config.noInlineConfig === true;
  435. const ignoreInlineConfig = providedOptions.allowInlineConfig === false;
  436. const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig
  437. ? ` (${config.configNameOfNoInlineConfig})`
  438. : "";
  439. let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives;
  440. if (typeof reportUnusedDisableDirectives === "boolean") {
  441. reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off";
  442. }
  443. if (typeof reportUnusedDisableDirectives !== "string") {
  444. reportUnusedDisableDirectives = config.reportUnusedDisableDirectives ? "warn" : "off";
  445. }
  446. return {
  447. filename: normalizeFilename(providedOptions.filename || "<input>"),
  448. allowInlineConfig: !ignoreInlineConfig,
  449. warnInlineConfig: disableInlineConfig && !ignoreInlineConfig
  450. ? `your config${configNameOfNoInlineConfig}`
  451. : null,
  452. reportUnusedDisableDirectives,
  453. disableFixes: Boolean(providedOptions.disableFixes)
  454. };
  455. }
  456. /**
  457. * Combines the provided parserOptions with the options from environments
  458. * @param {Parser} parser The parser which uses this options.
  459. * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
  460. * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  461. * @returns {ParserOptions} Resulting parser options after merge
  462. */
  463. function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
  464. const parserOptionsFromEnv = enabledEnvironments
  465. .filter(env => env.parserOptions)
  466. .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
  467. const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
  468. const isModule = mergedParserOptions.sourceType === "module";
  469. if (isModule) {
  470. /*
  471. * can't have global return inside of modules
  472. * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add)
  473. */
  474. mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
  475. }
  476. mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);
  477. return mergedParserOptions;
  478. }
  479. /**
  480. * Combines the provided globals object with the globals from environments
  481. * @param {Record<string, GlobalConf>} providedGlobals The 'globals' key in a config
  482. * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
  483. * @returns {Record<string, GlobalConf>} The resolved globals object
  484. */
  485. function resolveGlobals(providedGlobals, enabledEnvironments) {
  486. return Object.assign(
  487. {},
  488. ...enabledEnvironments.filter(env => env.globals).map(env => env.globals),
  489. providedGlobals
  490. );
  491. }
  492. /**
  493. * Strips Unicode BOM from a given text.
  494. * @param {string} text A text to strip.
  495. * @returns {string} The stripped text.
  496. */
  497. function stripUnicodeBOM(text) {
  498. /*
  499. * Check Unicode BOM.
  500. * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
  501. * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
  502. */
  503. if (text.charCodeAt(0) === 0xFEFF) {
  504. return text.slice(1);
  505. }
  506. return text;
  507. }
  508. /**
  509. * Get the options for a rule (not including severity), if any
  510. * @param {Array|number} ruleConfig rule configuration
  511. * @returns {Array} of rule options, empty Array if none
  512. */
  513. function getRuleOptions(ruleConfig) {
  514. if (Array.isArray(ruleConfig)) {
  515. return ruleConfig.slice(1);
  516. }
  517. return [];
  518. }
  519. /**
  520. * Analyze scope of the given AST.
  521. * @param {ASTNode} ast The `Program` node to analyze.
  522. * @param {ParserOptions} parserOptions The parser options.
  523. * @param {Record<string, string[]>} visitorKeys The visitor keys.
  524. * @returns {ScopeManager} The analysis result.
  525. */
  526. function analyzeScope(ast, parserOptions, visitorKeys) {
  527. const ecmaFeatures = parserOptions.ecmaFeatures || {};
  528. const ecmaVersion = parserOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
  529. return eslintScope.analyze(ast, {
  530. ignoreEval: true,
  531. nodejsScope: ecmaFeatures.globalReturn,
  532. impliedStrict: ecmaFeatures.impliedStrict,
  533. ecmaVersion,
  534. sourceType: parserOptions.sourceType || "script",
  535. childVisitorKeys: visitorKeys || evk.KEYS,
  536. fallback: Traverser.getKeys
  537. });
  538. }
  539. /**
  540. * Parses text into an AST. Moved out here because the try-catch prevents
  541. * optimization of functions, so it's best to keep the try-catch as isolated
  542. * as possible
  543. * @param {string} text The text to parse.
  544. * @param {Parser} parser The parser to parse.
  545. * @param {ParserOptions} providedParserOptions Options to pass to the parser
  546. * @param {string} filePath The path to the file being parsed.
  547. * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
  548. * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
  549. * @private
  550. */
  551. function parse(text, parser, providedParserOptions, filePath) {
  552. const textToParse = stripUnicodeBOM(text).replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
  553. const parserOptions = Object.assign({}, providedParserOptions, {
  554. loc: true,
  555. range: true,
  556. raw: true,
  557. tokens: true,
  558. comment: true,
  559. eslintVisitorKeys: true,
  560. eslintScopeManager: true,
  561. filePath
  562. });
  563. /*
  564. * Check for parsing errors first. If there's a parsing error, nothing
  565. * else can happen. However, a parsing error does not throw an error
  566. * from this method - it's just considered a fatal error message, a
  567. * problem that ESLint identified just like any other.
  568. */
  569. try {
  570. const parseResult = (typeof parser.parseForESLint === "function")
  571. ? parser.parseForESLint(textToParse, parserOptions)
  572. : { ast: parser.parse(textToParse, parserOptions) };
  573. const ast = parseResult.ast;
  574. const parserServices = parseResult.services || {};
  575. const visitorKeys = parseResult.visitorKeys || evk.KEYS;
  576. const scopeManager = parseResult.scopeManager || analyzeScope(ast, parserOptions, visitorKeys);
  577. return {
  578. success: true,
  579. /*
  580. * Save all values that `parseForESLint()` returned.
  581. * If a `SourceCode` object is given as the first parameter instead of source code text,
  582. * linter skips the parsing process and reuses the source code object.
  583. * In that case, linter needs all the values that `parseForESLint()` returned.
  584. */
  585. sourceCode: new SourceCode({
  586. text,
  587. ast,
  588. parserServices,
  589. scopeManager,
  590. visitorKeys
  591. })
  592. };
  593. } catch (ex) {
  594. // If the message includes a leading line number, strip it:
  595. const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
  596. debug("%s\n%s", message, ex.stack);
  597. return {
  598. success: false,
  599. error: {
  600. ruleId: null,
  601. fatal: true,
  602. severity: 2,
  603. message,
  604. line: ex.lineNumber,
  605. column: ex.column
  606. }
  607. };
  608. }
  609. }
  610. /**
  611. * Gets the scope for the current node
  612. * @param {ScopeManager} scopeManager The scope manager for this AST
  613. * @param {ASTNode} currentNode The node to get the scope of
  614. * @returns {eslint-scope.Scope} The scope information for this node
  615. */
  616. function getScope(scopeManager, currentNode) {
  617. // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
  618. const inner = currentNode.type !== "Program";
  619. for (let node = currentNode; node; node = node.parent) {
  620. const scope = scopeManager.acquire(node, inner);
  621. if (scope) {
  622. if (scope.type === "function-expression-name") {
  623. return scope.childScopes[0];
  624. }
  625. return scope;
  626. }
  627. }
  628. return scopeManager.scopes[0];
  629. }
  630. /**
  631. * Marks a variable as used in the current scope
  632. * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function.
  633. * @param {ASTNode} currentNode The node currently being traversed
  634. * @param {Object} parserOptions The options used to parse this text
  635. * @param {string} name The name of the variable that should be marked as used.
  636. * @returns {boolean} True if the variable was found and marked as used, false if not.
  637. */
  638. function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) {
  639. const hasGlobalReturn = parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn;
  640. const specialScope = hasGlobalReturn || parserOptions.sourceType === "module";
  641. const currentScope = getScope(scopeManager, currentNode);
  642. // Special Node.js scope means we need to start one level deeper
  643. const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope;
  644. for (let scope = initialScope; scope; scope = scope.upper) {
  645. const variable = scope.variables.find(scopeVar => scopeVar.name === name);
  646. if (variable) {
  647. variable.eslintUsed = true;
  648. return true;
  649. }
  650. }
  651. return false;
  652. }
  653. /**
  654. * Runs a rule, and gets its listeners
  655. * @param {Rule} rule A normalized rule with a `create` method
  656. * @param {Context} ruleContext The context that should be passed to the rule
  657. * @returns {Object} A map of selector listeners provided by the rule
  658. */
  659. function createRuleListeners(rule, ruleContext) {
  660. try {
  661. return rule.create(ruleContext);
  662. } catch (ex) {
  663. ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`;
  664. throw ex;
  665. }
  666. }
  667. /**
  668. * Gets all the ancestors of a given node
  669. * @param {ASTNode} node The node
  670. * @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting
  671. * from the root node and going inwards to the parent node.
  672. */
  673. function getAncestors(node) {
  674. const ancestorsStartingAtParent = [];
  675. for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
  676. ancestorsStartingAtParent.push(ancestor);
  677. }
  678. return ancestorsStartingAtParent.reverse();
  679. }
  680. // methods that exist on SourceCode object
  681. const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
  682. getSource: "getText",
  683. getSourceLines: "getLines",
  684. getAllComments: "getAllComments",
  685. getNodeByRangeIndex: "getNodeByRangeIndex",
  686. getComments: "getComments",
  687. getCommentsBefore: "getCommentsBefore",
  688. getCommentsAfter: "getCommentsAfter",
  689. getCommentsInside: "getCommentsInside",
  690. getJSDocComment: "getJSDocComment",
  691. getFirstToken: "getFirstToken",
  692. getFirstTokens: "getFirstTokens",
  693. getLastToken: "getLastToken",
  694. getLastTokens: "getLastTokens",
  695. getTokenAfter: "getTokenAfter",
  696. getTokenBefore: "getTokenBefore",
  697. getTokenByRangeStart: "getTokenByRangeStart",
  698. getTokens: "getTokens",
  699. getTokensAfter: "getTokensAfter",
  700. getTokensBefore: "getTokensBefore",
  701. getTokensBetween: "getTokensBetween"
  702. };
  703. const BASE_TRAVERSAL_CONTEXT = Object.freeze(
  704. Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
  705. (contextInfo, methodName) =>
  706. Object.assign(contextInfo, {
  707. [methodName](...args) {
  708. return this.getSourceCode()[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args);
  709. }
  710. }),
  711. {}
  712. )
  713. );
  714. /**
  715. * Runs the given rules on the given SourceCode object
  716. * @param {SourceCode} sourceCode A SourceCode object for the given text
  717. * @param {Object} configuredRules The rules configuration
  718. * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
  719. * @param {Object} parserOptions The options that were passed to the parser
  720. * @param {string} parserName The name of the parser in the config
  721. * @param {Object} settings The settings that were enabled in the config
  722. * @param {string} filename The reported filename of the code
  723. * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
  724. * @param {string | undefined} cwd cwd of the cli
  725. * @param {string} physicalFilename The full path of the file on disk without any code block information
  726. * @returns {Problem[]} An array of reported problems
  727. */
  728. function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd, physicalFilename) {
  729. const emitter = createEmitter();
  730. const nodeQueue = [];
  731. let currentNode = sourceCode.ast;
  732. Traverser.traverse(sourceCode.ast, {
  733. enter(node, parent) {
  734. node.parent = parent;
  735. nodeQueue.push({ isEntering: true, node });
  736. },
  737. leave(node) {
  738. nodeQueue.push({ isEntering: false, node });
  739. },
  740. visitorKeys: sourceCode.visitorKeys
  741. });
  742. /*
  743. * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
  744. * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
  745. * properties once for each rule.
  746. */
  747. const sharedTraversalContext = Object.freeze(
  748. Object.assign(
  749. Object.create(BASE_TRAVERSAL_CONTEXT),
  750. {
  751. getAncestors: () => getAncestors(currentNode),
  752. getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
  753. getCwd: () => cwd,
  754. getFilename: () => filename,
  755. getPhysicalFilename: () => physicalFilename || filename,
  756. getScope: () => getScope(sourceCode.scopeManager, currentNode),
  757. getSourceCode: () => sourceCode,
  758. markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, parserOptions, name),
  759. parserOptions,
  760. parserPath: parserName,
  761. parserServices: sourceCode.parserServices,
  762. settings
  763. }
  764. )
  765. );
  766. const lintingProblems = [];
  767. Object.keys(configuredRules).forEach(ruleId => {
  768. const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
  769. // not load disabled rules
  770. if (severity === 0) {
  771. return;
  772. }
  773. const rule = ruleMapper(ruleId);
  774. if (rule === null) {
  775. lintingProblems.push(createLintingProblem({ ruleId }));
  776. return;
  777. }
  778. const messageIds = rule.meta && rule.meta.messages;
  779. let reportTranslator = null;
  780. const ruleContext = Object.freeze(
  781. Object.assign(
  782. Object.create(sharedTraversalContext),
  783. {
  784. id: ruleId,
  785. options: getRuleOptions(configuredRules[ruleId]),
  786. report(...args) {
  787. /*
  788. * Create a report translator lazily.
  789. * In a vast majority of cases, any given rule reports zero errors on a given
  790. * piece of code. Creating a translator lazily avoids the performance cost of
  791. * creating a new translator function for each rule that usually doesn't get
  792. * called.
  793. *
  794. * Using lazy report translators improves end-to-end performance by about 3%
  795. * with Node 8.4.0.
  796. */
  797. if (reportTranslator === null) {
  798. reportTranslator = createReportTranslator({
  799. ruleId,
  800. severity,
  801. sourceCode,
  802. messageIds,
  803. disableFixes
  804. });
  805. }
  806. const problem = reportTranslator(...args);
  807. if (problem.fix && rule.meta && !rule.meta.fixable) {
  808. throw new Error("Fixable rules should export a `meta.fixable` property.");
  809. }
  810. lintingProblems.push(problem);
  811. }
  812. }
  813. )
  814. );
  815. const ruleListeners = createRuleListeners(rule, ruleContext);
  816. // add all the selectors from the rule as listeners
  817. Object.keys(ruleListeners).forEach(selector => {
  818. emitter.on(
  819. selector,
  820. timing.enabled
  821. ? timing.time(ruleId, ruleListeners[selector])
  822. : ruleListeners[selector]
  823. );
  824. });
  825. });
  826. // only run code path analyzer if the top level node is "Program", skip otherwise
  827. const eventGenerator = nodeQueue[0].node.type === "Program"
  828. ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
  829. : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
  830. nodeQueue.forEach(traversalInfo => {
  831. currentNode = traversalInfo.node;
  832. try {
  833. if (traversalInfo.isEntering) {
  834. eventGenerator.enterNode(currentNode);
  835. } else {
  836. eventGenerator.leaveNode(currentNode);
  837. }
  838. } catch (err) {
  839. err.currentNode = currentNode;
  840. throw err;
  841. }
  842. });
  843. return lintingProblems;
  844. }
  845. /**
  846. * Ensure the source code to be a string.
  847. * @param {string|SourceCode} textOrSourceCode The text or source code object.
  848. * @returns {string} The source code text.
  849. */
  850. function ensureText(textOrSourceCode) {
  851. if (typeof textOrSourceCode === "object") {
  852. const { hasBOM, text } = textOrSourceCode;
  853. const bom = hasBOM ? "\uFEFF" : "";
  854. return bom + text;
  855. }
  856. return String(textOrSourceCode);
  857. }
  858. /**
  859. * Get an environment.
  860. * @param {LinterInternalSlots} slots The internal slots of Linter.
  861. * @param {string} envId The environment ID to get.
  862. * @returns {Environment|null} The environment.
  863. */
  864. function getEnv(slots, envId) {
  865. return (
  866. (slots.lastConfigArray && slots.lastConfigArray.pluginEnvironments.get(envId)) ||
  867. BuiltInEnvironments.get(envId) ||
  868. null
  869. );
  870. }
  871. /**
  872. * Get a rule.
  873. * @param {LinterInternalSlots} slots The internal slots of Linter.
  874. * @param {string} ruleId The rule ID to get.
  875. * @returns {Rule|null} The rule.
  876. */
  877. function getRule(slots, ruleId) {
  878. return (
  879. (slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) ||
  880. slots.ruleMap.get(ruleId)
  881. );
  882. }
  883. /**
  884. * Normalize the value of the cwd
  885. * @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.
  886. * @returns {string | undefined} normalized cwd
  887. */
  888. function normalizeCwd(cwd) {
  889. if (cwd) {
  890. return cwd;
  891. }
  892. if (typeof process === "object") {
  893. return process.cwd();
  894. }
  895. // It's more explicit to assign the undefined
  896. // eslint-disable-next-line no-undefined
  897. return undefined;
  898. }
  899. /**
  900. * The map to store private data.
  901. * @type {WeakMap<Linter, LinterInternalSlots>}
  902. */
  903. const internalSlotsMap = new WeakMap();
  904. //------------------------------------------------------------------------------
  905. // Public Interface
  906. //------------------------------------------------------------------------------
  907. /**
  908. * Object that is responsible for verifying JavaScript text
  909. * @name eslint
  910. */
  911. class Linter {
  912. /**
  913. * Initialize the Linter.
  914. * @param {Object} [config] the config object
  915. * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
  916. */
  917. constructor({ cwd } = {}) {
  918. internalSlotsMap.set(this, {
  919. cwd: normalizeCwd(cwd),
  920. lastConfigArray: null,
  921. lastSourceCode: null,
  922. parserMap: new Map([["espree", espree]]),
  923. ruleMap: new Rules()
  924. });
  925. this.version = pkg.version;
  926. }
  927. /**
  928. * Getter for package version.
  929. * @static
  930. * @returns {string} The version from package.json.
  931. */
  932. static get version() {
  933. return pkg.version;
  934. }
  935. /**
  936. * Same as linter.verify, except without support for processors.
  937. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  938. * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
  939. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
  940. * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
  941. */
  942. _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
  943. const slots = internalSlotsMap.get(this);
  944. const config = providedConfig || {};
  945. const options = normalizeVerifyOptions(providedOptions, config);
  946. let text;
  947. // evaluate arguments
  948. if (typeof textOrSourceCode === "string") {
  949. slots.lastSourceCode = null;
  950. text = textOrSourceCode;
  951. } else {
  952. slots.lastSourceCode = textOrSourceCode;
  953. text = textOrSourceCode.text;
  954. }
  955. // Resolve parser.
  956. let parserName = DEFAULT_PARSER_NAME;
  957. let parser = espree;
  958. if (typeof config.parser === "object" && config.parser !== null) {
  959. parserName = config.parser.filePath;
  960. parser = config.parser.definition;
  961. } else if (typeof config.parser === "string") {
  962. if (!slots.parserMap.has(config.parser)) {
  963. return [{
  964. ruleId: null,
  965. fatal: true,
  966. severity: 2,
  967. message: `Configured parser '${config.parser}' was not found.`,
  968. line: 0,
  969. column: 0
  970. }];
  971. }
  972. parserName = config.parser;
  973. parser = slots.parserMap.get(config.parser);
  974. }
  975. // search and apply "eslint-env *".
  976. const envInFile = options.allowInlineConfig && !options.warnInlineConfig
  977. ? findEslintEnv(text)
  978. : {};
  979. const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
  980. const enabledEnvs = Object.keys(resolvedEnvConfig)
  981. .filter(envName => resolvedEnvConfig[envName])
  982. .map(envName => getEnv(slots, envName))
  983. .filter(env => env);
  984. const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
  985. const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
  986. const settings = config.settings || {};
  987. if (!slots.lastSourceCode) {
  988. const parseResult = parse(
  989. text,
  990. parser,
  991. parserOptions,
  992. options.filename
  993. );
  994. if (!parseResult.success) {
  995. return [parseResult.error];
  996. }
  997. slots.lastSourceCode = parseResult.sourceCode;
  998. } else {
  999. /*
  1000. * If the given source code object as the first argument does not have scopeManager, analyze the scope.
  1001. * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
  1002. */
  1003. if (!slots.lastSourceCode.scopeManager) {
  1004. slots.lastSourceCode = new SourceCode({
  1005. text: slots.lastSourceCode.text,
  1006. ast: slots.lastSourceCode.ast,
  1007. parserServices: slots.lastSourceCode.parserServices,
  1008. visitorKeys: slots.lastSourceCode.visitorKeys,
  1009. scopeManager: analyzeScope(slots.lastSourceCode.ast, parserOptions)
  1010. });
  1011. }
  1012. }
  1013. const sourceCode = slots.lastSourceCode;
  1014. const commentDirectives = options.allowInlineConfig
  1015. ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
  1016. : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
  1017. // augment global scope with declared global variables
  1018. addDeclaredGlobals(
  1019. sourceCode.scopeManager.scopes[0],
  1020. configuredGlobals,
  1021. { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
  1022. );
  1023. const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
  1024. let lintingProblems;
  1025. try {
  1026. lintingProblems = runRules(
  1027. sourceCode,
  1028. configuredRules,
  1029. ruleId => getRule(slots, ruleId),
  1030. parserOptions,
  1031. parserName,
  1032. settings,
  1033. options.filename,
  1034. options.disableFixes,
  1035. slots.cwd,
  1036. providedOptions.physicalFilename
  1037. );
  1038. } catch (err) {
  1039. err.message += `\nOccurred while linting ${options.filename}`;
  1040. debug("An error occurred while traversing");
  1041. debug("Filename:", options.filename);
  1042. if (err.currentNode) {
  1043. const { line } = err.currentNode.loc.start;
  1044. debug("Line:", line);
  1045. err.message += `:${line}`;
  1046. }
  1047. debug("Parser Options:", parserOptions);
  1048. debug("Parser Path:", parserName);
  1049. debug("Settings:", settings);
  1050. throw err;
  1051. }
  1052. return applyDisableDirectives({
  1053. directives: commentDirectives.disableDirectives,
  1054. problems: lintingProblems
  1055. .concat(commentDirectives.problems)
  1056. .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
  1057. reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
  1058. });
  1059. }
  1060. /**
  1061. * Verifies the text against the rules specified by the second argument.
  1062. * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
  1063. * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything.
  1064. * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked.
  1065. * If this is not set, the filename will default to '<input>' in the rule context. If
  1066. * an object, then it has "filename", "allowInlineConfig", and some properties.
  1067. * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
  1068. */
  1069. verify(textOrSourceCode, config, filenameOrOptions) {
  1070. debug("Verify");
  1071. const options = typeof filenameOrOptions === "string"
  1072. ? { filename: filenameOrOptions }
  1073. : filenameOrOptions || {};
  1074. // CLIEngine passes a `ConfigArray` object.
  1075. if (config && typeof config.extractConfig === "function") {
  1076. return this._verifyWithConfigArray(textOrSourceCode, config, options);
  1077. }
  1078. /*
  1079. * `Linter` doesn't support `overrides` property in configuration.
  1080. * So we cannot apply multiple processors.
  1081. */
  1082. if (options.preprocess || options.postprocess) {
  1083. return this._verifyWithProcessor(textOrSourceCode, config, options);
  1084. }
  1085. return this._verifyWithoutProcessors(textOrSourceCode, config, options);
  1086. }
  1087. /**
  1088. * Verify a given code with `ConfigArray`.
  1089. * @param {string|SourceCode} textOrSourceCode The source code.
  1090. * @param {ConfigArray} configArray The config array.
  1091. * @param {VerifyOptions&ProcessorOptions} options The options.
  1092. * @returns {LintMessage[]} The found problems.
  1093. */
  1094. _verifyWithConfigArray(textOrSourceCode, configArray, options) {
  1095. debug("With ConfigArray: %s", options.filename);
  1096. // Store the config array in order to get plugin envs and rules later.
  1097. internalSlotsMap.get(this).lastConfigArray = configArray;
  1098. // Extract the final config for this file.
  1099. const config = configArray.extractConfig(options.filename);
  1100. const processor =
  1101. config.processor &&
  1102. configArray.pluginProcessors.get(config.processor);
  1103. // Verify.
  1104. if (processor) {
  1105. debug("Apply the processor: %o", config.processor);
  1106. const { preprocess, postprocess, supportsAutofix } = processor;
  1107. const disableFixes = options.disableFixes || !supportsAutofix;
  1108. return this._verifyWithProcessor(
  1109. textOrSourceCode,
  1110. config,
  1111. { ...options, disableFixes, postprocess, preprocess },
  1112. configArray
  1113. );
  1114. }
  1115. return this._verifyWithoutProcessors(textOrSourceCode, config, options);
  1116. }
  1117. /**
  1118. * Verify with a processor.
  1119. * @param {string|SourceCode} textOrSourceCode The source code.
  1120. * @param {ConfigData|ExtractedConfig} config The config array.
  1121. * @param {VerifyOptions&ProcessorOptions} options The options.
  1122. * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
  1123. * @returns {LintMessage[]} The found problems.
  1124. */
  1125. _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
  1126. const filename = options.filename || "<input>";
  1127. const filenameToExpose = normalizeFilename(filename);
  1128. const physicalFilename = options.physicalFilename || filenameToExpose;
  1129. const text = ensureText(textOrSourceCode);
  1130. const preprocess = options.preprocess || (rawText => [rawText]);
  1131. // TODO(stephenwade): Replace this with array.flat() when we drop support for Node v10
  1132. const postprocess = options.postprocess || (array => [].concat(...array));
  1133. const filterCodeBlock =
  1134. options.filterCodeBlock ||
  1135. (blockFilename => blockFilename.endsWith(".js"));
  1136. const originalExtname = path.extname(filename);
  1137. const messageLists = preprocess(text, filenameToExpose).map((block, i) => {
  1138. debug("A code block was found: %o", block.filename || "(unnamed)");
  1139. // Keep the legacy behavior.
  1140. if (typeof block === "string") {
  1141. return this._verifyWithoutProcessors(block, config, options);
  1142. }
  1143. const blockText = block.text;
  1144. const blockName = path.join(filename, `${i}_${block.filename}`);
  1145. // Skip this block if filtered.
  1146. if (!filterCodeBlock(blockName, blockText)) {
  1147. debug("This code block was skipped.");
  1148. return [];
  1149. }
  1150. // Resolve configuration again if the file content or extension was changed.
  1151. if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
  1152. debug("Resolving configuration again because the file content or extension was changed.");
  1153. return this._verifyWithConfigArray(
  1154. blockText,
  1155. configForRecursive,
  1156. { ...options, filename: blockName, physicalFilename }
  1157. );
  1158. }
  1159. // Does lint.
  1160. return this._verifyWithoutProcessors(
  1161. blockText,
  1162. config,
  1163. { ...options, filename: blockName, physicalFilename }
  1164. );
  1165. });
  1166. return postprocess(messageLists, filenameToExpose);
  1167. }
  1168. /**
  1169. * Gets the SourceCode object representing the parsed source.
  1170. * @returns {SourceCode} The SourceCode object.
  1171. */
  1172. getSourceCode() {
  1173. return internalSlotsMap.get(this).lastSourceCode;
  1174. }
  1175. /**
  1176. * Defines a new linting rule.
  1177. * @param {string} ruleId A unique rule identifier
  1178. * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers
  1179. * @returns {void}
  1180. */
  1181. defineRule(ruleId, ruleModule) {
  1182. internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule);
  1183. }
  1184. /**
  1185. * Defines many new linting rules.
  1186. * @param {Record<string, Function | Rule>} rulesToDefine map from unique rule identifier to rule
  1187. * @returns {void}
  1188. */
  1189. defineRules(rulesToDefine) {
  1190. Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
  1191. this.defineRule(ruleId, rulesToDefine[ruleId]);
  1192. });
  1193. }
  1194. /**
  1195. * Gets an object with all loaded rules.
  1196. * @returns {Map<string, Rule>} All loaded rules
  1197. */
  1198. getRules() {
  1199. const { lastConfigArray, ruleMap } = internalSlotsMap.get(this);
  1200. return new Map(function *() {
  1201. yield* ruleMap;
  1202. if (lastConfigArray) {
  1203. yield* lastConfigArray.pluginRules;
  1204. }
  1205. }());
  1206. }
  1207. /**
  1208. * Define a new parser module
  1209. * @param {string} parserId Name of the parser
  1210. * @param {Parser} parserModule The parser object
  1211. * @returns {void}
  1212. */
  1213. defineParser(parserId, parserModule) {
  1214. internalSlotsMap.get(this).parserMap.set(parserId, parserModule);
  1215. }
  1216. /**
  1217. * Performs multiple autofix passes over the text until as many fixes as possible
  1218. * have been applied.
  1219. * @param {string} text The source text to apply fixes to.
  1220. * @param {ConfigData|ConfigArray} config The ESLint config object to use.
  1221. * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use.
  1222. * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the
  1223. * SourceCodeFixer.
  1224. */
  1225. verifyAndFix(text, config, options) {
  1226. let messages = [],
  1227. fixedResult,
  1228. fixed = false,
  1229. passNumber = 0,
  1230. currentText = text;
  1231. const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
  1232. const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
  1233. /**
  1234. * This loop continues until one of the following is true:
  1235. *
  1236. * 1. No more fixes have been applied.
  1237. * 2. Ten passes have been made.
  1238. *
  1239. * That means anytime a fix is successfully applied, there will be another pass.
  1240. * Essentially, guaranteeing a minimum of two passes.
  1241. */
  1242. do {
  1243. passNumber++;
  1244. debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
  1245. messages = this.verify(currentText, config, options);
  1246. debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
  1247. fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
  1248. /*
  1249. * stop if there are any syntax errors.
  1250. * 'fixedResult.output' is a empty string.
  1251. */
  1252. if (messages.length === 1 && messages[0].fatal) {
  1253. break;
  1254. }
  1255. // keep track if any fixes were ever applied - important for return value
  1256. fixed = fixed || fixedResult.fixed;
  1257. // update to use the fixed output instead of the original text
  1258. currentText = fixedResult.output;
  1259. } while (
  1260. fixedResult.fixed &&
  1261. passNumber < MAX_AUTOFIX_PASSES
  1262. );
  1263. /*
  1264. * If the last result had fixes, we need to lint again to be sure we have
  1265. * the most up-to-date information.
  1266. */
  1267. if (fixedResult.fixed) {
  1268. fixedResult.messages = this.verify(currentText, config, options);
  1269. }
  1270. // ensure the last result properly reflects if fixes were done
  1271. fixedResult.fixed = fixed;
  1272. fixedResult.output = currentText;
  1273. return fixedResult;
  1274. }
  1275. }
  1276. module.exports = {
  1277. Linter,
  1278. /**
  1279. * Get the internal slots of a given Linter instance for tests.
  1280. * @param {Linter} instance The Linter instance to get.
  1281. * @returns {LinterInternalSlots} The internal slots.
  1282. */
  1283. getLinterInternalSlots(instance) {
  1284. return internalSlotsMap.get(instance);
  1285. }
  1286. };