index.js 9.4 KB

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