index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. /**
  2. * @fileoverview JavaScript Language Object
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //-----------------------------------------------------------------------------
  7. // Requirements
  8. //-----------------------------------------------------------------------------
  9. const { SourceCode } = require("./source-code");
  10. const createDebug = require("debug");
  11. const astUtils = require("../../shared/ast-utils");
  12. const espree = require("espree");
  13. const eslintScope = require("eslint-scope");
  14. const evk = require("eslint-visitor-keys");
  15. const { validateLanguageOptions } = require("./validate-language-options");
  16. const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version");
  17. //-----------------------------------------------------------------------------
  18. // Type Definitions
  19. //-----------------------------------------------------------------------------
  20. /** @typedef {import("@eslint/core").File} File */
  21. /** @typedef {import("@eslint/core").Language} Language */
  22. /** @typedef {import("@eslint/core").OkParseResult} OkParseResult */
  23. /** @typedef {import("../../types").Linter.LanguageOptions} JSLanguageOptions */
  24. //-----------------------------------------------------------------------------
  25. // Helpers
  26. //-----------------------------------------------------------------------------
  27. const debug = createDebug("eslint:languages:js");
  28. const DEFAULT_ECMA_VERSION = 5;
  29. const parserSymbol = Symbol.for("eslint.RuleTester.parser");
  30. /**
  31. * Analyze scope of the given AST.
  32. * @param {ASTNode} ast The `Program` node to analyze.
  33. * @param {JSLanguageOptions} languageOptions The parser options.
  34. * @param {Record<string, string[]>} visitorKeys The visitor keys.
  35. * @returns {ScopeManager} The analysis result.
  36. */
  37. function analyzeScope(ast, languageOptions, visitorKeys) {
  38. const parserOptions = languageOptions.parserOptions;
  39. const ecmaFeatures = parserOptions.ecmaFeatures || {};
  40. const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
  41. return eslintScope.analyze(ast, {
  42. ignoreEval: true,
  43. nodejsScope: ecmaFeatures.globalReturn,
  44. impliedStrict: ecmaFeatures.impliedStrict,
  45. ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
  46. sourceType: languageOptions.sourceType || "script",
  47. childVisitorKeys: visitorKeys || evk.KEYS,
  48. fallback: evk.getKeys,
  49. });
  50. }
  51. /**
  52. * Determines if a given object is Espree.
  53. * @param {Object} parser The parser to check.
  54. * @returns {boolean} True if the parser is Espree or false if not.
  55. */
  56. function isEspree(parser) {
  57. return !!(parser === espree || parser[parserSymbol] === espree);
  58. }
  59. /**
  60. * Normalize ECMAScript version from the initial config into languageOptions (year)
  61. * format.
  62. * @param {any} [ecmaVersion] ECMAScript version from the initial config
  63. * @returns {number} normalized ECMAScript version
  64. */
  65. function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
  66. switch (ecmaVersion) {
  67. case 3:
  68. return 3;
  69. // void 0 = no ecmaVersion specified so use the default
  70. case 5:
  71. case void 0:
  72. return 5;
  73. default:
  74. if (typeof ecmaVersion === "number") {
  75. return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
  76. }
  77. }
  78. /*
  79. * We default to the latest supported ecmaVersion for everything else.
  80. * Remember, this is for languageOptions.ecmaVersion, which sets the version
  81. * that is used for a number of processes inside of ESLint. It's normally
  82. * safe to assume people want the latest unless otherwise specified.
  83. */
  84. return LATEST_ECMA_VERSION;
  85. }
  86. //-----------------------------------------------------------------------------
  87. // Exports
  88. //-----------------------------------------------------------------------------
  89. /**
  90. * @type {Language}
  91. */
  92. module.exports = {
  93. fileType: "text",
  94. lineStart: 1,
  95. columnStart: 0,
  96. nodeTypeKey: "type",
  97. visitorKeys: evk.KEYS,
  98. defaultLanguageOptions: {
  99. sourceType: "module",
  100. ecmaVersion: "latest",
  101. parser: espree,
  102. parserOptions: {},
  103. },
  104. validateLanguageOptions,
  105. /**
  106. * Normalizes the language options.
  107. * @param {Object} languageOptions The language options to normalize.
  108. * @returns {Object} The normalized language options.
  109. */
  110. normalizeLanguageOptions(languageOptions) {
  111. languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
  112. languageOptions.ecmaVersion,
  113. );
  114. // Espree expects this information to be passed in
  115. if (isEspree(languageOptions.parser)) {
  116. const parserOptions = languageOptions.parserOptions;
  117. if (languageOptions.sourceType) {
  118. parserOptions.sourceType = languageOptions.sourceType;
  119. if (
  120. parserOptions.sourceType === "module" &&
  121. parserOptions.ecmaFeatures &&
  122. parserOptions.ecmaFeatures.globalReturn
  123. ) {
  124. parserOptions.ecmaFeatures.globalReturn = false;
  125. }
  126. }
  127. }
  128. return languageOptions;
  129. },
  130. /**
  131. * Determines if a given node matches a given selector class.
  132. * @param {string} className The class name to check.
  133. * @param {ASTNode} node The node to check.
  134. * @param {Array<ASTNode>} ancestry The ancestry of the node.
  135. * @returns {boolean} True if there's a match, false if not.
  136. * @throws {Error} When an unknown class name is passed.
  137. */
  138. matchesSelectorClass(className, node, ancestry) {
  139. /*
  140. * Copyright (c) 2013, Joel Feenstra
  141. * All rights reserved.
  142. *
  143. * Redistribution and use in source and binary forms, with or without
  144. * modification, are permitted provided that the following conditions are met:
  145. * * Redistributions of source code must retain the above copyright
  146. * notice, this list of conditions and the following disclaimer.
  147. * * Redistributions in binary form must reproduce the above copyright
  148. * notice, this list of conditions and the following disclaimer in the
  149. * documentation and/or other materials provided with the distribution.
  150. * * Neither the name of the ESQuery nor the names of its contributors may
  151. * be used to endorse or promote products derived from this software without
  152. * specific prior written permission.
  153. *
  154. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  155. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  156. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  157. * DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY
  158. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  159. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  160. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  161. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  162. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  163. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  164. */
  165. switch (className.toLowerCase()) {
  166. case "statement":
  167. if (node.type.slice(-9) === "Statement") {
  168. return true;
  169. }
  170. // fallthrough: interface Declaration <: Statement { }
  171. case "declaration":
  172. return node.type.slice(-11) === "Declaration";
  173. case "pattern":
  174. if (node.type.slice(-7) === "Pattern") {
  175. return true;
  176. }
  177. // fallthrough: interface Expression <: Node, Pattern { }
  178. case "expression":
  179. return (
  180. node.type.slice(-10) === "Expression" ||
  181. node.type.slice(-7) === "Literal" ||
  182. (node.type === "Identifier" &&
  183. (ancestry.length === 0 ||
  184. ancestry[0].type !== "MetaProperty")) ||
  185. node.type === "MetaProperty"
  186. );
  187. case "function":
  188. return (
  189. node.type === "FunctionDeclaration" ||
  190. node.type === "FunctionExpression" ||
  191. node.type === "ArrowFunctionExpression"
  192. );
  193. default:
  194. throw new Error(`Unknown class name: ${className}`);
  195. }
  196. },
  197. /**
  198. * Parses the given file into an AST.
  199. * @param {File} file The virtual file to parse.
  200. * @param {Object} options Additional options passed from ESLint.
  201. * @param {JSLanguageOptions} options.languageOptions The language options.
  202. * @returns {Object} The result of parsing.
  203. */
  204. parse(file, { languageOptions }) {
  205. // Note: BOM already removed
  206. const { body: text, path: filePath } = file;
  207. const textToParse = text.replace(
  208. astUtils.shebangPattern,
  209. (match, captured) => `//${captured}`,
  210. );
  211. const { ecmaVersion, sourceType, parser } = languageOptions;
  212. const parserOptions = Object.assign(
  213. { ecmaVersion, sourceType },
  214. languageOptions.parserOptions,
  215. {
  216. loc: true,
  217. range: true,
  218. raw: true,
  219. tokens: true,
  220. comment: true,
  221. eslintVisitorKeys: true,
  222. eslintScopeManager: true,
  223. filePath,
  224. },
  225. );
  226. /*
  227. * Check for parsing errors first. If there's a parsing error, nothing
  228. * else can happen. However, a parsing error does not throw an error
  229. * from this method - it's just considered a fatal error message, a
  230. * problem that ESLint identified just like any other.
  231. */
  232. try {
  233. debug("Parsing:", filePath);
  234. const parseResult =
  235. typeof parser.parseForESLint === "function"
  236. ? parser.parseForESLint(textToParse, parserOptions)
  237. : { ast: parser.parse(textToParse, parserOptions) };
  238. debug("Parsing successful:", filePath);
  239. const {
  240. ast,
  241. services: parserServices = {},
  242. visitorKeys = evk.KEYS,
  243. scopeManager,
  244. } = parseResult;
  245. return {
  246. ok: true,
  247. ast,
  248. parserServices,
  249. visitorKeys,
  250. scopeManager,
  251. };
  252. } catch (ex) {
  253. // If the message includes a leading line number, strip it:
  254. const message = ex.message.replace(/^line \d+:/iu, "").trim();
  255. debug("%s\n%s", message, ex.stack);
  256. return {
  257. ok: false,
  258. errors: [
  259. {
  260. message,
  261. line: ex.lineNumber,
  262. column: ex.column,
  263. },
  264. ],
  265. };
  266. }
  267. },
  268. /**
  269. * Creates a new `SourceCode` object from the given information.
  270. * @param {File} file The virtual file to create a `SourceCode` object from.
  271. * @param {OkParseResult} parseResult The result returned from `parse()`.
  272. * @param {Object} options Additional options passed from ESLint.
  273. * @param {JSLanguageOptions} options.languageOptions The language options.
  274. * @returns {SourceCode} The new `SourceCode` object.
  275. */
  276. createSourceCode(file, parseResult, { languageOptions }) {
  277. const { body: text, path: filePath, bom: hasBOM } = file;
  278. const { ast, parserServices, visitorKeys } = parseResult;
  279. debug("Scope analysis:", filePath);
  280. const scopeManager =
  281. parseResult.scopeManager ||
  282. analyzeScope(ast, languageOptions, visitorKeys);
  283. debug("Scope analysis successful:", filePath);
  284. return new SourceCode({
  285. text,
  286. ast,
  287. hasBOM,
  288. parserServices,
  289. scopeManager,
  290. visitorKeys,
  291. });
  292. },
  293. };