123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- /**
- * @fileoverview JavaScript Language Object
- * @author Nicholas C. Zakas
- */
- "use strict";
- //-----------------------------------------------------------------------------
- // 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");
- //-----------------------------------------------------------------------------
- // Type Definitions
- //-----------------------------------------------------------------------------
- /** @typedef {import("@eslint/core").File} File */
- /** @typedef {import("@eslint/core").Language} Language */
- /** @typedef {import("@eslint/core").OkParseResult} OkParseResult */
- //-----------------------------------------------------------------------------
- // Helpers
- //-----------------------------------------------------------------------------
- const debug = createDebug("eslint:languages:js");
- const DEFAULT_ECMA_VERSION = 5;
- /**
- * Analyze scope of the given AST.
- * @param {ASTNode} ast The `Program` node to analyze.
- * @param {LanguageOptions} 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
- });
- }
- //-----------------------------------------------------------------------------
- // 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,
- /**
- * 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 {LanguageOptions} 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,
- raw: 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 {LanguageOptions} 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
- });
- }
- };
|