Spaces:
Runtime error
Runtime error
| /** | |
| * @fileoverview JavaScript Language Object | |
| * @author Nicholas C. Zakas | |
| */ | |
| ; | |
| //----------------------------------------------------------------------------- | |
| // Requirements | |
| //----------------------------------------------------------------------------- | |
| const { SourceCode } = require("./source-code"); | |
| const createDebug = require("debug"); | |
| const astUtils = require("../../shared/ast-utils"); | |
| const espree = require("espree"); | |
| const eslintScope = require("eslint-scope"); | |
| const evk = require("eslint-visitor-keys"); | |
| const { validateLanguageOptions } = require("./validate-language-options"); | |
| const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version"); | |
| //----------------------------------------------------------------------------- | |
| // Type Definitions | |
| //----------------------------------------------------------------------------- | |
| /** @typedef {import("@eslint/core").File} File */ | |
| /** @typedef {import("@eslint/core").Language} Language */ | |
| /** @typedef {import("@eslint/core").OkParseResult} OkParseResult */ | |
| /** @typedef {import("../../types").Linter.LanguageOptions} JSLanguageOptions */ | |
| //----------------------------------------------------------------------------- | |
| // Helpers | |
| //----------------------------------------------------------------------------- | |
| const debug = createDebug("eslint:languages:js"); | |
| const DEFAULT_ECMA_VERSION = 5; | |
| const parserSymbol = Symbol.for("eslint.RuleTester.parser"); | |
| /** | |
| * Analyze scope of the given AST. | |
| * @param {ASTNode} ast The `Program` node to analyze. | |
| * @param {JSLanguageOptions} languageOptions The parser options. | |
| * @param {Record<string, string[]>} visitorKeys The visitor keys. | |
| * @returns {ScopeManager} The analysis result. | |
| */ | |
| function analyzeScope(ast, languageOptions, visitorKeys) { | |
| const parserOptions = languageOptions.parserOptions; | |
| const ecmaFeatures = parserOptions.ecmaFeatures || {}; | |
| const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION; | |
| return eslintScope.analyze(ast, { | |
| ignoreEval: true, | |
| nodejsScope: ecmaFeatures.globalReturn, | |
| impliedStrict: ecmaFeatures.impliedStrict, | |
| ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6, | |
| sourceType: languageOptions.sourceType || "script", | |
| childVisitorKeys: visitorKeys || evk.KEYS, | |
| fallback: evk.getKeys, | |
| jsx: ecmaFeatures.jsx, | |
| }); | |
| } | |
| /** | |
| * Determines if a given object is Espree. | |
| * @param {Object} parser The parser to check. | |
| * @returns {boolean} True if the parser is Espree or false if not. | |
| */ | |
| function isEspree(parser) { | |
| return !!(parser === espree || parser[parserSymbol] === espree); | |
| } | |
| /** | |
| * Normalize ECMAScript version from the initial config into languageOptions (year) | |
| * format. | |
| * @param {any} [ecmaVersion] ECMAScript version from the initial config | |
| * @returns {number} normalized ECMAScript version | |
| */ | |
| function normalizeEcmaVersionForLanguageOptions(ecmaVersion) { | |
| switch (ecmaVersion) { | |
| case 3: | |
| return 3; | |
| // void 0 = no ecmaVersion specified so use the default | |
| case 5: | |
| case void 0: | |
| return 5; | |
| default: | |
| if (typeof ecmaVersion === "number") { | |
| return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009; | |
| } | |
| } | |
| /* | |
| * We default to the latest supported ecmaVersion for everything else. | |
| * Remember, this is for languageOptions.ecmaVersion, which sets the version | |
| * that is used for a number of processes inside of ESLint. It's normally | |
| * safe to assume people want the latest unless otherwise specified. | |
| */ | |
| return LATEST_ECMA_VERSION; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Exports | |
| //----------------------------------------------------------------------------- | |
| /** | |
| * @type {Language} | |
| */ | |
| module.exports = { | |
| fileType: "text", | |
| lineStart: 1, | |
| columnStart: 0, | |
| nodeTypeKey: "type", | |
| visitorKeys: evk.KEYS, | |
| defaultLanguageOptions: { | |
| sourceType: "module", | |
| ecmaVersion: "latest", | |
| parser: espree, | |
| parserOptions: {}, | |
| }, | |
| validateLanguageOptions, | |
| /** | |
| * Normalizes the language options. | |
| * @param {Object} languageOptions The language options to normalize. | |
| * @returns {Object} The normalized language options. | |
| */ | |
| normalizeLanguageOptions(languageOptions) { | |
| languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions( | |
| languageOptions.ecmaVersion, | |
| ); | |
| // Espree expects this information to be passed in | |
| if (isEspree(languageOptions.parser)) { | |
| const parserOptions = languageOptions.parserOptions; | |
| if (languageOptions.sourceType) { | |
| parserOptions.sourceType = languageOptions.sourceType; | |
| if ( | |
| parserOptions.sourceType === "module" && | |
| parserOptions.ecmaFeatures && | |
| parserOptions.ecmaFeatures.globalReturn | |
| ) { | |
| parserOptions.ecmaFeatures.globalReturn = false; | |
| } | |
| } | |
| } | |
| return languageOptions; | |
| }, | |
| /** | |
| * Determines if a given node matches a given selector class. | |
| * @param {string} className The class name to check. | |
| * @param {ASTNode} node The node to check. | |
| * @param {Array<ASTNode>} ancestry The ancestry of the node. | |
| * @returns {boolean} True if there's a match, false if not. | |
| * @throws {Error} When an unknown class name is passed. | |
| */ | |
| matchesSelectorClass(className, node, ancestry) { | |
| /* | |
| * Copyright (c) 2013, Joel Feenstra | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * * Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * * Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * * Neither the name of the ESQuery nor the names of its contributors may | |
| * be used to endorse or promote products derived from this software without | |
| * specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| * DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY | |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| switch (className.toLowerCase()) { | |
| case "statement": | |
| if (node.type.slice(-9) === "Statement") { | |
| return true; | |
| } | |
| // fallthrough: interface Declaration <: Statement { } | |
| case "declaration": | |
| return node.type.slice(-11) === "Declaration"; | |
| case "pattern": | |
| if (node.type.slice(-7) === "Pattern") { | |
| return true; | |
| } | |
| // fallthrough: interface Expression <: Node, Pattern { } | |
| case "expression": | |
| return ( | |
| node.type.slice(-10) === "Expression" || | |
| node.type.slice(-7) === "Literal" || | |
| (node.type === "Identifier" && | |
| (ancestry.length === 0 || | |
| ancestry[0].type !== "MetaProperty")) || | |
| node.type === "MetaProperty" | |
| ); | |
| case "function": | |
| return ( | |
| node.type === "FunctionDeclaration" || | |
| node.type === "FunctionExpression" || | |
| node.type === "ArrowFunctionExpression" | |
| ); | |
| default: | |
| throw new Error(`Unknown class name: ${className}`); | |
| } | |
| }, | |
| /** | |
| * Parses the given file into an AST. | |
| * @param {File} file The virtual file to parse. | |
| * @param {Object} options Additional options passed from ESLint. | |
| * @param {JSLanguageOptions} options.languageOptions The language options. | |
| * @returns {Object} The result of parsing. | |
| */ | |
| parse(file, { languageOptions }) { | |
| // Note: BOM already removed | |
| const { body: text, path: filePath } = file; | |
| const textToParse = text.replace( | |
| astUtils.shebangPattern, | |
| (match, captured) => `//${captured}`, | |
| ); | |
| const { ecmaVersion, sourceType, parser } = languageOptions; | |
| const parserOptions = Object.assign( | |
| { ecmaVersion, sourceType }, | |
| languageOptions.parserOptions, | |
| { | |
| loc: true, | |
| range: true, | |
| tokens: true, | |
| comment: true, | |
| eslintVisitorKeys: true, | |
| eslintScopeManager: true, | |
| filePath, | |
| }, | |
| ); | |
| /* | |
| * Check for parsing errors first. If there's a parsing error, nothing | |
| * else can happen. However, a parsing error does not throw an error | |
| * from this method - it's just considered a fatal error message, a | |
| * problem that ESLint identified just like any other. | |
| */ | |
| try { | |
| debug("Parsing:", filePath); | |
| const parseResult = | |
| typeof parser.parseForESLint === "function" | |
| ? parser.parseForESLint(textToParse, parserOptions) | |
| : { ast: parser.parse(textToParse, parserOptions) }; | |
| debug("Parsing successful:", filePath); | |
| const { | |
| ast, | |
| services: parserServices = {}, | |
| visitorKeys = evk.KEYS, | |
| scopeManager, | |
| } = parseResult; | |
| return { | |
| ok: true, | |
| ast, | |
| parserServices, | |
| visitorKeys, | |
| scopeManager, | |
| }; | |
| } catch (ex) { | |
| // If the message includes a leading line number, strip it: | |
| const message = ex.message.replace(/^line \d+:/iu, "").trim(); | |
| debug("%s\n%s", message, ex.stack); | |
| return { | |
| ok: false, | |
| errors: [ | |
| { | |
| message, | |
| line: ex.lineNumber, | |
| column: ex.column, | |
| }, | |
| ], | |
| }; | |
| } | |
| }, | |
| /** | |
| * Creates a new `SourceCode` object from the given information. | |
| * @param {File} file The virtual file to create a `SourceCode` object from. | |
| * @param {OkParseResult} parseResult The result returned from `parse()`. | |
| * @param {Object} options Additional options passed from ESLint. | |
| * @param {JSLanguageOptions} options.languageOptions The language options. | |
| * @returns {SourceCode} The new `SourceCode` object. | |
| */ | |
| createSourceCode(file, parseResult, { languageOptions }) { | |
| const { body: text, path: filePath, bom: hasBOM } = file; | |
| const { ast, parserServices, visitorKeys } = parseResult; | |
| debug("Scope analysis:", filePath); | |
| const scopeManager = | |
| parseResult.scopeManager || | |
| analyzeScope(ast, languageOptions, visitorKeys); | |
| debug("Scope analysis successful:", filePath); | |
| return new SourceCode({ | |
| text, | |
| ast, | |
| hasBOM, | |
| parserServices, | |
| scopeManager, | |
| visitorKeys, | |
| }); | |
| }, | |
| }; | |