123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- /**
- * @fileoverview functions for scanning an AST for globals including
- * traversing referenced scripts.
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- "use strict";
- const path = require("path");
- const fs = require("fs");
- const helpers = require("./helpers");
- const escope = require("escope");
- const estraverse = require("estraverse");
- /**
- * Parses a list of "name:boolean_value" or/and "name" options divided by comma or
- * whitespace.
- *
- * This function was copied from eslint.js
- *
- * @param {string} string The string to parse.
- * @param {Comment} comment The comment node which has the string.
- * @returns {Object} Result map object of names and boolean values
- */
- function parseBooleanConfig(string, comment) {
- let items = {};
- // Collapse whitespace around : to make parsing easier
- string = string.replace(/\s*:\s*/g, ":");
- // Collapse whitespace around ,
- string = string.replace(/\s*,\s*/g, ",");
- string.split(/\s|,+/).forEach(function(name) {
- if (!name) {
- return;
- }
- let pos = name.indexOf(":");
- let value = undefined;
- if (pos !== -1) {
- value = name.substring(pos + 1, name.length);
- name = name.substring(0, pos);
- }
- items[name] = {
- value: (value === "true"),
- comment: comment
- };
- });
- return items;
- }
- /**
- * Global discovery can require parsing many files. This map of
- * {String} => {Object} caches what globals were discovered for a file path.
- */
- const globalCache = new Map();
- /**
- * An object that returns found globals for given AST node types. Each prototype
- * property should be named for a node type and accepts a node parameter and a
- * parents parameter which is a list of the parent nodes of the current node.
- * Each returns an array of globals found.
- *
- * @param {String} path
- * The absolute path of the file being parsed.
- */
- function GlobalsForNode(path) {
- this.path = path;
- this.root = helpers.getRootDir(path);
- }
- GlobalsForNode.prototype = {
- BlockComment(node, parents) {
- let value = node.value.trim();
- let match = /^import-globals-from\s+(.+)$/.exec(value);
- if (!match) {
- return [];
- }
- let filePath = match[1].trim();
- if (!path.isAbsolute(filePath)) {
- let dirName = path.dirname(this.path);
- filePath = path.resolve(dirName, filePath);
- }
- return module.exports.getGlobalsForFile(filePath);
- },
- ExpressionStatement(node, parents) {
- let isGlobal = helpers.getIsGlobalScope(parents);
- let names = helpers.convertExpressionToGlobals(node, isGlobal, this.root);
- return names.map(name => { return { name, writable: true }});
- },
- };
- module.exports = {
- /**
- * Returns all globals for a given file. Recursively searches through
- * import-globals-from directives and also includes globals defined by
- * standard eslint directives.
- *
- * @param {String} path
- * The absolute path of the file to be parsed.
- */
- getGlobalsForFile(path) {
- if (globalCache.has(path)) {
- return globalCache.get(path);
- }
- let content = fs.readFileSync(path, "utf8");
- // Parse the content into an AST
- let ast = helpers.getAST(content);
- // Discover global declarations
- let scopeManager = escope.analyze(ast);
- let globalScope = scopeManager.acquire(ast);
- let globals = Object.keys(globalScope.variables).map(v => ({
- name: globalScope.variables[v].name,
- writable: true,
- }));
- // Walk over the AST to find any of our custom globals
- let handler = new GlobalsForNode(path);
- helpers.walkAST(ast, (type, node, parents) => {
- // We have to discover any globals that ESLint would have defined through
- // comment directives
- if (type == "BlockComment") {
- let value = node.value.trim();
- let match = /^globals?\s+(.+)$/.exec(value);
- if (match) {
- let values = parseBooleanConfig(match[1].trim(), node);
- for (let name of Object.keys(values)) {
- globals.push({
- name,
- writable: values[name].value
- })
- }
- }
- }
- if (type in handler) {
- let newGlobals = handler[type](node, parents);
- globals.push.apply(globals, newGlobals);
- }
- });
- globalCache.set(path, globals);
- return globals;
- },
- /**
- * Intended to be used as-is for an ESLint rule that parses for globals in
- * the current file and recurses through import-globals-from directives.
- *
- * @param {Object} context
- * The ESLint parsing context.
- */
- getESLintGlobalParser(context) {
- let globalScope;
- let parser = {
- Program(node) {
- globalScope = context.getScope();
- }
- };
- // Install thin wrappers around GlobalsForNode
- let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
- for (let type of Object.keys(GlobalsForNode.prototype)) {
- parser[type] = function(node) {
- let globals = handler[type](node, context.getAncestors());
- helpers.addGlobals(globals, globalScope);
- }
- }
- return parser;
- }
- };
|