diff --git a/packages/eslint-plugin/docs/rules/naming-convention.md b/packages/eslint-plugin/docs/rules/naming-convention.md index 99823a164c1..77661230523 100644 --- a/packages/eslint-plugin/docs/rules/naming-convention.md +++ b/packages/eslint-plugin/docs/rules/naming-convention.md @@ -40,7 +40,12 @@ type Options = { // selector options selector: Selector; - filter?: string; + filter?: + | string + | { + regex: string; + match: boolean; + }; // the allowed values for these are dependent on the selector - see below modifiers?: Modifiers[]; types?: Types[]; @@ -118,6 +123,19 @@ Accepts an object with the following properties: - `regex` - accepts a regular expression (anything accepted into `new RegExp(regex)`). - `match` - true if the identifier _must_ match the `regex`, false if the identifier _must not_ match the `regex`. +### `filter` + +The `filter` option operates similar to `custom`, accepting the same shaped object, except that it controls if the rest of the configuration should or should not be applied to an identifier. + +You can use this to include or exclude specific identifiers from specific configurations. + +Accepts an object with the following properties: + +- `regex` - accepts a regular expression (anything accepted into `new RegExp(regex)`). +- `match` - true if the identifier _must_ match the `regex`, false if the identifier _must not_ match the `regex`. + +Alternatively, `filter` accepts a regular expression (anything accepted into `new RegExp(filter)`). In this case, it's treated as if you had passed an object with the regex and `match: true`. + #### `leadingUnderscore` / `trailingUnderscore` The `leadingUnderscore` / `trailingUnderscore` options control whether leading/trailing underscores are considered valid. Accepts one of the following values: @@ -135,7 +153,6 @@ If these are provided, the identifier must start with one of the provided values ### Selector Options - `selector` (see "Allowed Selectors, Modifiers and Types" below). -- `filter` accepts a regular expression (anything accepted into `new RegExp(filter)`). It allows you to limit the scope of this configuration to names that match this regex. - `modifiers` allows you to specify which modifiers to granularly apply to, such as the accessibility (`private`/`public`/`protected`), or if the thing is `static`, etc. - The name must match _all_ of the modifiers. - For example, if you provide `{ modifiers: ['private', 'static', 'readonly'] }`, then it will only match something that is `private static readonly`, and something that is just `private` will not match. diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index 1938d383a50..f1963b17321 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -113,7 +113,12 @@ interface Selector { selector: IndividualAndMetaSelectorsString; modifiers?: ModifiersString[]; types?: TypeModifiersString[]; - filter?: string; + filter?: + | string + | { + regex: string; + match: boolean; + }; } interface NormalizedSelector { // format options @@ -130,7 +135,10 @@ interface NormalizedSelector { selector: Selectors | MetaSelectors; modifiers: Modifiers[] | null; types: TypeModifiers[] | null; - filter: RegExp | null; + filter: { + regex: RegExp; + match: boolean; + } | null; // calculated ordering weight based on modifiers modifierWeight: number; } @@ -156,6 +164,14 @@ const PREFIX_SUFFIX_SCHEMA: JSONSchema.JSONSchema4 = { }, additionalItems: false, }; +const MATCH_REGEX_SCHEMA: JSONSchema.JSONSchema4 = { + type: 'object', + properties: { + match: { type: 'boolean' }, + regex: { type: 'string' }, + }, + required: ['match', 'regex'], +}; type JSONSchemaProperties = Record; const FORMAT_OPTIONS_PROPERTIES: JSONSchemaProperties = { format: { @@ -173,18 +189,7 @@ const FORMAT_OPTIONS_PROPERTIES: JSONSchemaProperties = { }, ], }, - custom: { - type: 'object', - properties: { - regex: { - type: 'string', - }, - match: { - type: 'boolean', - }, - }, - required: ['regex', 'match'], - }, + custom: MATCH_REGEX_SCHEMA, leadingUnderscore: UNDERSCORE_SCHEMA, trailingUnderscore: UNDERSCORE_SCHEMA, prefix: PREFIX_SUFFIX_SCHEMA, @@ -197,8 +202,13 @@ function selectorSchema( ): JSONSchema.JSONSchema4[] { const selector: JSONSchemaProperties = { filter: { - type: 'string', - minLength: 1, + oneOf: [ + { + type: 'string', + minLength: 1, + }, + MATCH_REGEX_SCHEMA, + ], }, selector: { type: 'string', @@ -797,7 +807,7 @@ function createValidator( // return will break the loop and stop checking configs // it is only used when the name is known to have failed or succeeded a config. for (const config of configs) { - if (config.filter?.test(originalName) === false) { + if (config.filter?.regex.test(originalName) !== config.filter?.match) { // name does not match the filter continue; } @@ -1216,7 +1226,15 @@ function normalizeOption(option: Selector): NormalizedSelector { : Selectors[option.selector], modifiers: option.modifiers?.map(m => Modifiers[m]) ?? null, types: option.types?.map(m => TypeModifiers[m]) ?? null, - filter: option.filter !== undefined ? new RegExp(option.filter) : null, + filter: + option.filter !== undefined + ? typeof option.filter === 'string' + ? { regex: new RegExp(option.filter), match: true } + : { + regex: new RegExp(option.filter.regex), + match: option.filter.match, + } + : null, // calculated ordering weight based on modifiers modifierWeight: weight, }; diff --git a/packages/eslint-plugin/tests/rules/naming-convention.test.ts b/packages/eslint-plugin/tests/rules/naming-convention.test.ts index f8270225e58..0df5f73d487 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention.test.ts @@ -80,7 +80,11 @@ const formatTestNames: Readonly[] { options: [ { ...options, - filter: IGNORED_REGEX.source, + filter: IGNORED_FILTER, }, ], code: `// ${JSON.stringify(options)}\n${test.code @@ -206,7 +210,7 @@ function createInvalidTestCases( options: [ { ...options, - filter: IGNORED_REGEX.source, + filter: IGNORED_FILTER, }, ], code: `// ${JSON.stringify(options)}\n${test.code @@ -606,6 +610,22 @@ ruleTester.run('naming-convention', rule, { valid: [ `const x = 1;`, // no options shouldn't crash ...createValidTestCases(cases), + { + code: ` + const child_process = require('child_process'); + `, + parserOptions, + options: [ + { + selector: 'default', + format: ['camelCase'], + filter: { + regex: 'child_process', + match: false, + }, + }, + ], + }, { code: ` declare const string_camelCase: string; @@ -742,6 +762,23 @@ ruleTester.run('naming-convention', rule, { ], invalid: [ ...createInvalidTestCases(cases), + { + code: ` + const child_process = require('child_process'); + `, + parserOptions, + options: [ + { + selector: 'default', + format: ['camelCase'], + filter: { + regex: 'child_process', + match: true, + }, + }, + ], + errors: [{ messageId: 'doesNotMatchFormat' }], + }, { code: ` declare const string_camelCase01: string;