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