1/** 2 * @fileoverview Config Comment Parser 3 * @author Nicholas C. Zakas 4 */ 5 6/* eslint-disable class-methods-use-this*/ 7"use strict"; 8 9//------------------------------------------------------------------------------ 10// Requirements 11//------------------------------------------------------------------------------ 12 13const levn = require("levn"), 14 ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"); 15 16const debug = require("debug")("eslint:config-comment-parser"); 17 18//------------------------------------------------------------------------------ 19// Public Interface 20//------------------------------------------------------------------------------ 21 22/** 23 * Object to parse ESLint configuration comments inside JavaScript files. 24 * @name ConfigCommentParser 25 */ 26module.exports = class ConfigCommentParser { 27 28 /** 29 * Parses a list of "name:string_value" or/and "name" options divided by comma or 30 * whitespace. Used for "global" and "exported" comments. 31 * @param {string} string The string to parse. 32 * @param {Comment} comment The comment node which has the string. 33 * @returns {Object} Result map object of names and string values, or null values if no value was provided 34 */ 35 parseStringConfig(string, comment) { 36 debug("Parsing String config"); 37 38 const items = {}; 39 40 // Collapse whitespace around `:` and `,` to make parsing easier 41 const trimmedString = string.replace(/\s*([:,])\s*/gu, "$1"); 42 43 trimmedString.split(/\s|,+/u).forEach(name => { 44 if (!name) { 45 return; 46 } 47 48 // value defaults to null (if not provided), e.g: "foo" => ["foo", null] 49 const [key, value = null] = name.split(":"); 50 51 items[key] = { value, comment }; 52 }); 53 return items; 54 } 55 56 /** 57 * Parses a JSON-like config. 58 * @param {string} string The string to parse. 59 * @param {Object} location Start line and column of comments for potential error message. 60 * @returns {({success: true, config: Object}|{success: false, error: Problem})} Result map object 61 */ 62 parseJsonConfig(string, location) { 63 debug("Parsing JSON config"); 64 65 let items = {}; 66 67 // Parses a JSON-like comment by the same way as parsing CLI option. 68 try { 69 items = levn.parse("Object", string) || {}; 70 71 // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`. 72 // Also, commaless notations have invalid severity: 73 // "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"} 74 // Should ignore that case as well. 75 if (ConfigOps.isEverySeverityValid(items)) { 76 return { 77 success: true, 78 config: items 79 }; 80 } 81 } catch { 82 83 debug("Levn parsing failed; falling back to manual parsing."); 84 85 // ignore to parse the string by a fallback. 86 } 87 88 /* 89 * Optionator cannot parse commaless notations. 90 * But we are supporting that. So this is a fallback for that. 91 */ 92 items = {}; 93 const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,"); 94 95 try { 96 items = JSON.parse(`{${normalizedString}}`); 97 } catch (ex) { 98 debug("Manual parsing failed."); 99 100 return { 101 success: false, 102 error: { 103 ruleId: null, 104 fatal: true, 105 severity: 2, 106 message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`, 107 line: location.start.line, 108 column: location.start.column + 1 109 } 110 }; 111 112 } 113 114 return { 115 success: true, 116 config: items 117 }; 118 } 119 120 /** 121 * Parses a config of values separated by comma. 122 * @param {string} string The string to parse. 123 * @returns {Object} Result map of values and true values 124 */ 125 parseListConfig(string) { 126 debug("Parsing list config"); 127 128 const items = {}; 129 130 // Collapse whitespace around commas 131 string.replace(/\s*,\s*/gu, ",").split(/,+/u).forEach(name => { 132 const trimmedName = name.trim(); 133 134 if (trimmedName) { 135 items[trimmedName] = true; 136 } 137 }); 138 return items; 139 } 140 141}; 142