📄 flat-config-schema.ts
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 10 |
🧱 Classes | 4 |
📦 Imports | 3 |
📊 Variables & Constants | 21 |
📐 Interfaces | 1 |
📑 Type Aliases | 3 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/rule-tester/src/utils/flat-config-schema.ts
📦 Imports¶
Name | Source |
---|---|
Processor |
@typescript-eslint/utils/ts-eslint |
SharedConfig |
@typescript-eslint/utils/ts-eslint |
normalizeSeverityToNumber |
./severity |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
ruleSeverities |
Map<SharedConfig.RuleLevel, SharedConfig.Severity> |
const | `new Map |
|
['error', 2], | ||||
['off', 0], | ||||
['warn', 1], | ||||
[0, 0], | ||||
[1, 1], | ||||
[2, 2], | ||||
])` | ✗ | |||
result |
First & ObjectLike & Second |
const | `{ | |
...first, | ||||
...second, | ||||
} as First & ObjectLike & Second` | ✗ | |||
firstValue |
object |
const | (first as ObjectLike)[key] as object | undefined |
✗ |
secondValue |
object |
const | (second as ObjectLike)[key] as object | undefined |
✗ |
finalOptions |
any[] |
const | `Array.isArray(ruleOptions) | |
? [...ruleOptions] | ||||
: [ruleOptions]` | ✗ | |||
booleanSchema |
{ merge: string; validate: string; } |
const | `{ | |
merge: 'replace', | ||||
validate: 'boolean', | ||||
} satisfies ObjectPropertySchema` | ✗ | |||
ALLOWED_SEVERITIES |
Set<string | number> |
const | new Set([0, 1, 2, 'error', 'off', 'warn']) |
✗ |
value |
any |
const | second ?? first |
✗ |
disableDirectiveSeveritySchema |
ObjectPropertySchema<SharedConfig.RuleLevel> |
const | `{ | |
merge( | ||||
first: boolean | SharedConfig.RuleLevel | undefined, | ||
second: boolean | SharedConfig.RuleLevel | undefined, | ||
): SharedConfig.RuleLevel { | ||||
const value = second ?? first; |
if (typeof value === 'boolean') {
return value ? 'warn' : 'off';
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return normalizeSeverityToNumber(value!);
},
validate(value: unknown) {
if (
!(
ALLOWED_SEVERITIES.has(value as number | string) ||
typeof value === 'boolean'
)
) {
throw new TypeError(
'Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean.',
);
}
},
}| ✗ |
|
deepObjectAssignSchema|
| const |
{
merge| ✗ |
|
languageOptionsSchema|
| const |
{
merge(first: ObjectLike = {}, second: ObjectLike = {}): object {
const result = deepMerge(first, second);
for (const [key, value] of Object.entries(result)) {
/*
* Special case: Because the `parser` property is an object, it should
* not be deep merged. Instead, it should be replaced if it exists in
* the second object. To make this more generic, we just check for
* objects with methods and replace them if they exist in the second
* object.
*/
if (isNonArrayObject(value)) {
if (hasMethod(value as ObjectLike)) {
result[key] = second[key] ?? first[key];
continue;
}
// for other objects, make sure we aren't reusing the same object
result[key] = { ...(result[key] as ObjectLike) };
continue;
}
}
return result;
},
validate: 'object',
}| ✗ |
|
languageSchema|
ObjectPropertySchema| const |
{
merge: 'replace',
validate: assertIsPluginMemberName,
}| ✗ |
|
keys|
Set| const |
new Set([...Object.keys(first), ...Object.keys(second)])| ✗ |
|
result|
ObjectLike| const |
{}| ✗ |
|
pluginsSchema|
| const |
{
merge(first: ObjectLike = {}, second: ObjectLike = {}): object {
const keys = new Set([...Object.keys(first), ...Object.keys(second)]);
const result: ObjectLike = {};
// manually validate that plugins are not redefined
for (const key of keys) {
// avoid hairy edge case
if (key === '__proto__') {
continue;
}
if (key in first && key in second && first[key] !== second[key]) {
throw new TypeError(`Cannot redefine plugin "${key}".`);
}
result[key] = second[key] || first[key];
}
return result;
}, validate(value: unknown): void { // first check the value to be sure it's an object if (value == null || typeof value !== 'object') { throw new TypeError('Expected an object.'); }
// make sure it's not an array, which would mean eslintrc-style is used
if (Array.isArray(value)) {
throw new IncompatiblePluginsError(value as string[]);
}
// second check the keys to make sure they are objects
for (const key of Object.keys(value)) {
// avoid hairy edge case
if (key === '__proto__') {
continue;
}
if (
(value as ObjectLike)[key] == null ||
typeof (value as ObjectLike)[key] !== 'object'
) {
throw new TypeError(`Key "${key}": Expected an object.`);
}
}
},
}| ✗ |
|
processorSchema|
ObjectPropertySchema| const |
{
merge: 'replace',
validate(value: unknown) {
if (typeof value === 'string') {
assertIsPluginMemberName(value);
} else if (value && typeof value === 'object') {
if (
typeof (value as Processor.LooseProcessorModule).preprocess !==
'function' ||
typeof (value as Processor.LooseProcessorModule).postprocess !==
'function'
) {
throw new TypeError(
'Object must have a preprocess() and a postprocess() method.',
);
}
} else {
throw new TypeError('Expected an object or a string.');
}
},
}| ✗ |
|
result|
ConfigRules| const |
{
...first,
...second,
}| ✗ |
|
ruleOptions|
SharedConfig.RuleLevelAndOptions| const |
value[ruleId]| ✗ |
|
rulesSchema|
| const |
{
merge(first: ConfigRules = {}, second: ConfigRules = {}): ConfigRules {
const result: ConfigRules = {
...first,
...second,
};
for (const ruleId of Object.keys(result)) {
try {
// avoid hairy edge case
if (ruleId === '__proto__') {
delete result.__proto__;
continue;
}
result[ruleId] = normalizeRuleOptions(result[ruleId]);
/*
* If either rule config is missing, then the correct
* config is already present and we just need to normalize
* the severity.
*/
if (!(ruleId in first) || !(ruleId in second)) {
continue;
}
const firstRuleOptions = normalizeRuleOptions(first[ruleId]);
const secondRuleOptions = normalizeRuleOptions(second[ruleId]);
/*
* If the second rule config only has a severity (length of 1),
* then use that severity and keep the rest of the options from
* the first rule config.
*/
if (secondRuleOptions.length === 1) {
result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)];
continue;
}
/*
* In any other situation, then the second rule config takes
* precedence. That means the value at `result[ruleId]` is
* already correct and no further work is necessary.
*/
} catch (ex) {
throw new Error(`Key "${ruleId}": ${(ex as Error).message}`, {
cause: ex,
});
}
}
return result;
},
validate(value: ConfigRules): void { assertIsObject(value);
/*
* We are not checking the rule schema here because there is no
* guarantee that the rule definition is present at this point. Instead
* we wait and check the rule schema during the finalization step
* of calculating a config.
*/
for (const ruleId of Object.keys(value)) {
// avoid hairy edge case
if (ruleId === '__proto__') {
continue;
}
const ruleOptions = value[ruleId];
assertIsRuleOptions(ruleId, ruleOptions);
if (Array.isArray(ruleOptions)) {
assertIsRuleSeverity(ruleId, ruleOptions[0]);
} else {
assertIsRuleSeverity(ruleId, ruleOptions);
}
}
},
}| ✗ |
|
eslintrcKeys|
string[]| const |
[
'env',
'extends',
'globals',
'ignorePatterns',
'noInlineConfig',
'overrides',
'parser',
'parserOptions',
'reportUnusedDisableDirectives',
'root',
]| ✗ |
|
flatConfigSchema|
{ language: ObjectPropertySchema<${string}/${string}
>; languageOptions: { merge(first?: ObjectLike, second?: ObjectLike): object; validate: string; }; ... 8 more ...; $schema: { ...; }; }| const |
{
$schema: { type: 'string' },
// Original ESLint schemas from flat-config-schema.js
// eslintrc-style keys that should always error ...Object.fromEntries( eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)]), ),
// flat config keys language: languageSchema, languageOptions: languageOptionsSchema, linterOptions: { schema: { noInlineConfig: booleanSchema, reportUnusedDisableDirectives: disableDirectiveSeveritySchema, }, }, plugins: pluginsSchema, processor: processorSchema, rules: rulesSchema, settings: deepObjectAssignSchema,
// not in ESLint source, but seemingly relevant? defaultFilenames: { additionalProperties: false, properties: { ts: { type: 'string' }, tsx: { type: 'string' }, }, required: ['ts', 'tsx'], type: 'object', },
// @typescript-eslint/rule-tester extensions
dependencyConstraints: { additionalProperties: { type: 'string', }, type: 'object', }, files: { items: { type: 'string' }, type: 'array' }, }` | ✓ |
Functions¶
isNonNullObject(value: unknown): boolean
¶
Code
-
JSDoc:
-
Parameters:
value: unknown
- Return Type:
boolean
- Internal Comments:
isNonArrayObject(value: unknown): boolean
¶
Code
-
JSDoc:
-
Parameters:
value: unknown
- Return Type:
boolean
- Calls:
isNonNullObject
Array.isArray
deepMerge(first: First, second: Second, mergeMap: Map<First | Second, Map<First | Second, First & Second>>): First & Second
¶
Code
function deepMerge<First extends object, Second extends object>(
first: First,
second: Second,
mergeMap = new Map<First | Second, Map<First | Second, First & Second>>(),
): First & Second {
let secondMergeMap = mergeMap.get(first);
if (secondMergeMap) {
const result = secondMergeMap.get(second);
if (result) {
// If this combination of first and second arguments has been already visited, return the previously created result.
return result;
}
} else {
secondMergeMap = new Map();
mergeMap.set(first, secondMergeMap);
}
/*
* First create a result object where properties from the second object
* overwrite properties from the first. This sets up a baseline to use
* later rather than needing to inspect and change every property
* individually.
*/
const result = {
...first,
...second,
} as First & ObjectLike & Second;
delete (result as ObjectLike).__proto__; // don't merge own property "__proto__"
// Store the pending result for this combination of first and second arguments.
secondMergeMap.set(second, result);
for (const key of Object.keys(second)) {
// avoid hairy edge case
if (
key === '__proto__' ||
!Object.prototype.propertyIsEnumerable.call(first, key)
) {
continue;
}
const firstValue = (first as ObjectLike)[key] as object | undefined;
const secondValue = (second as ObjectLike)[key] as object | undefined;
if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) {
(result as ObjectLike)[key] = deepMerge(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
firstValue!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
secondValue!,
mergeMap,
);
// eslint-disable-next-line @typescript-eslint/internal/eqeq-nullish
} else if (secondValue === undefined) {
(result as ObjectLike)[key] = firstValue;
}
}
return result;
}
-
JSDoc:
-
Parameters:
first: First
second: Second
mergeMap: Map<First | Second, Map<First | Second, First & Second>>
- Return Type:
First & Second
- Calls:
mergeMap.get
secondMergeMap.get
mergeMap.set
secondMergeMap.set
Object.keys
Object.prototype.propertyIsEnumerable.call
isNonArrayObject
deepMerge
- Internal Comments:
// If this combination of first and second arguments has been already visited, return the previously created result. /* * First create a result object where properties from the second object * overwrite properties from the first. This sets up a baseline to use * later rather than needing to inspect and change every property * individually. */ (x2) // Store the pending result for this combination of first and second arguments. (x4) // avoid hairy edge case // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x4)
normalizeRuleOptions(ruleOptions: SharedConfig.RuleLevel | SharedConfig.RuleLevelAndOptions): SharedConfig.RuleLevelAndOptions
¶
Code
function normalizeRuleOptions(
ruleOptions: SharedConfig.RuleLevel | SharedConfig.RuleLevelAndOptions,
): SharedConfig.RuleLevelAndOptions {
const finalOptions = Array.isArray(ruleOptions)
? [...ruleOptions]
: [ruleOptions];
finalOptions[0] = ruleSeverities.get(
finalOptions[0] as SharedConfig.RuleLevel,
);
return structuredClone(finalOptions as SharedConfig.RuleLevelAndOptions);
}
-
JSDoc:
-
Parameters:
ruleOptions: SharedConfig.RuleLevel | SharedConfig.RuleLevelAndOptions
- Return Type:
SharedConfig.RuleLevelAndOptions
- Calls:
Array.isArray
ruleSeverities.get
structuredClone
hasMethod(object: Record<string, unknown>): boolean
¶
Code
-
JSDoc:
-
Parameters:
object: Record<string, unknown>
- Return Type:
boolean
- Calls:
Object.keys
assertIsRuleOptions(ruleId: string, value: unknown): void
¶
Code
-
JSDoc:
-
Parameters:
ruleId: string
value: unknown
- Return Type:
void
- Calls:
Array.isArray
assertIsRuleSeverity(ruleId: string, value: unknown): void
¶
Code
-
JSDoc:
-
Parameters:
ruleId: string
value: unknown
- Return Type:
void
- Calls:
ruleSeverities.get
assertIsPluginMemberName(value: unknown): asserts value is PluginMemberName
¶
Code
function assertIsPluginMemberName(
value: unknown,
): asserts value is PluginMemberName {
if (typeof value !== 'string' || !/[@\w$-]+(?:\/[\w$-]+)+$/iu.test(value)) {
throw new TypeError(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Expected string in the form "pluginName/objectName" but found "${value}".`,
);
}
}
-
JSDoc:
-
Parameters:
value: unknown
- Return Type:
asserts value is PluginMemberName
- Calls:
/[@\w$-]+(?:\/[\w$-]+)+$/iu.test
- Internal Comments:
assertIsObject(value: unknown): void
¶
Code
-
JSDoc:
-
Parameters:
value: unknown
- Return Type:
void
- Calls:
isNonNullObject
createEslintrcErrorSchema(key: string): ObjectPropertySchema
¶
Code
-
JSDoc:
-
Parameters:
key: string
- Return Type:
ObjectPropertySchema
Classes¶
InvalidRuleOptionsError
¶
Class Code
class InvalidRuleOptionsError extends Error {
readonly messageData: { ruleId: string; value: unknown };
readonly messageTemplate: string;
constructor(ruleId: string, value: unknown) {
super(
`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`,
);
this.messageTemplate = 'invalid-rule-options';
this.messageData = { ruleId, value };
}
}
InvalidRuleSeverityError
¶
Class Code
class InvalidRuleSeverityError extends Error {
readonly messageData: { ruleId: string; value: unknown };
readonly messageTemplate: string;
constructor(ruleId: string, value: unknown) {
super(
`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`,
);
this.messageTemplate = 'invalid-rule-severity';
this.messageData = { ruleId, value };
}
}
IncompatibleKeyError
¶
Class Code
class IncompatibleKeyError extends Error {
readonly messageData: { key: string };
readonly messageTemplate: string;
/**
* @param key The invalid key.
*/
constructor(key: string) {
super(
'This appears to be in eslintrc format rather than flat config format.',
);
this.messageTemplate = 'eslintrc-incompat';
this.messageData = { key };
}
}
IncompatiblePluginsError
¶
Class Code
class IncompatiblePluginsError extends Error {
readonly messageData: { plugins: string[] };
readonly messageTemplate: string;
constructor(plugins: string[]) {
super(
'This appears to be in eslintrc format (array of strings) rather than flat config format (object).',
);
this.messageTemplate = 'eslintrc-plugins';
this.messageData = { plugins };
}
}
Interfaces¶
ObjectPropertySchema<T = unknown>
¶
Interface Code
Properties¶
Name | Type | Optional | Description |
---|---|---|---|
merge |
string | ((a: T, b: T) => T) |
✗ | |
validate |
string | ((value: unknown) => asserts value is T) |
✗ |