10000 feat(eslint-plugin): support negative matches for `filter` (#1517) · Bertrand/typescript-eslint@b24fbe8 · GitHub
[go: up one dir, main page]

Skip to content

Commit b24fbe8

Browse files
G-Rathbradzacher
andauthored
feat(eslint-plugin): support negative matches for filter (typescript-eslint#1517)
Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
1 parent 6613fad commit b24fbe8

File tree

3 files changed

+95
-23
lines changed

3 files changed

+95
-23
lines changed

packages/eslint-plugin/docs/rules/naming-convention.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ type Options = {
4040

4141
// selector options
4242
selector: Selector;
43-
filter?: string;
43+
filter?:
44+
| string
45+
| {
46+
regex: string;
47+
match: boolean;
48+
};
4449
// the allowed values for these are dependent on the selector - see below
4550
modifiers?: Modifiers<Selector>[];
4651
types?: Types<Selector>[];
@@ -118,6 +123,19 @@ Accepts an object with the following properties:
118123
- `regex` - accepts a regular expression (anything accepted into `new RegExp(regex)`).
119124
- `match` - true if the identifier _must_ match the `regex`, false if the identifier _must not_ match the `regex`.
120125

126+
### `filter`
127+
128+
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.
129+
130+
You can use this to include or exclude specific identifiers from specific configurations.
131+
132+
Accepts an object with the following properties:
133+
134+
- `regex` - accepts a regular expression (anything accepted into `new RegExp(regex)`).
135+
- `match` - true if the identifier _must_ match the `regex`, false if the identifier _must not_ match the `regex`.
136+
137+
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`.
138+
121139
#### `leadingUnderscore` / `trailingUnderscore`
122140

123141
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
135153
### Selector Options
136154

137155
- `selector` (see "Allowed Selectors, Modifiers and Types" below).
138-
- `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.
139156
- `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.
140157
- The name must match _all_ of the modifiers.
141158
- 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.

packages/eslint-plugin/src/rules/naming-convention.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ interface Selector {
113113
selector: IndividualAndMetaSelectorsString;
114114
modifiers?: ModifiersString[];
115115
types?: TypeModifiersString[];
116-
filter?: string;
116+
filter?:
117+
| string
118+
| {
119+
regex: string;
120+
match: boolean;
121+
};
117122
}
118123
interface NormalizedSelector {
119124
// format options
@@ -130,7 +135,10 @@ interface NormalizedSelector {
130135
selector: Selectors | MetaSelectors;
131136
modifiers: Modifiers[] | null;
132137
types: TypeModifiers[] | null;
133-
filter: RegExp | null;
138+
filter: {
139+
regex: RegExp;
140+
match: boolean;
141+
} | null;
134142
// calculated ordering weight based on modifiers
135143
modifierWeight: number;
136144
}
@@ -156,6 +164,14 @@ const PREFIX_SUFFIX_SCHEMA: JSONSchema.JSONSchema4 = {
156164
},
157165
additionalItems: false,
158166
};
167+
const MATCH_REGEX_SCHEMA: JSONSchema.JSONSchema4 = {
168+
type: 'object',
169+
properties: {
170+
match: { type: 'boolean' },
171+
regex: { type: 'string' },
172+
},
173+
required: ['match', 'regex'],
174+
};
159175
type JSONSchemaProperties = Record<string, JSONSchema.JSONSchema4>;
160176
const FORMAT_OPTIONS_PROPERTIES: JSONSchemaProperties = {
161177
format: {
@@ -173,18 +189,7 @@ const FORMAT_OPTIONS_PROPERTIES: JSONSchemaProperties = {
173189
},
174190
],
175191
},
176-
custom: {
177-
type: 'object',
178-
properties: {
179-
regex: {
180-
type: 'string',
181-
},
182-
match: {
183-
type: 'boolean',
184-
},
185-
},
186-
required: ['regex', 'match'],
187-
},
192+
custom: MATCH_REGEX_SCHEMA,
188193
leadingUnderscore: UNDERSCORE_SCHEMA,
189194
trailingUnderscore: UNDERSCORE_SCHEMA,
190195
prefix: PREFIX_SUFFIX_SCHEMA,
@@ -197,8 +202,13 @@ function selectorSchema(
197202
): JSONSchema.JSONSchema4[] {
198203
const selector: JSONSchemaProperties = {
199204
filter: {
200-
type: 'string',
201-
minLength: 1,
205+
oneOf: [
206+
{
207+
type: 'string',
208+
minLength: 1,
209+
},
210+
MATCH_REGEX_SCHEMA,
211+
],
202212
},
203213
selector: {
204214
type: 'string',
@@ -797,7 +807,7 @@ function createValidator(
797807
// return will break the loop and stop checking configs
798808
// it is only used when the name is known to have failed or succeeded a config.
799809
for (const config of configs) {
800-
if (config.filter?.test(originalName) === false) {
810+
if (config.filter?.regex.test(originalName) !== config.filter?.match) {
801811
// name does not match the filter
802812
continue;
803813
}
@@ -1216,7 +1226,15 @@ function normalizeOption(option: Selector): NormalizedSelector {
12161226
: Selectors[option.selector],
12171227
modifiers: option.modifiers?.map(m => Modifiers[m]) ?? null,
12181228
types: option.types?.map(m => TypeModifiers[m]) ?? null,
1219-
filter: option.filter !== undefined ? new RegExp(option.filter) : null,
1229+
filter:
1230+
option.filter !== undefined
1231+
? typeof option.filter === 'string'
1232+
? { regex: new RegExp(option.filter), match: true }
1233+
: {
1234+
regex: new RegExp(option.filter.regex),
1235+
match: option.filter.match,
1236+
}
1237+
: null,
12201238
// calculated ordering weight based on modifiers
12211239
modifierWeight: weight,
12221240
};

packages/eslint-plugin/tests/rules/naming-convention.test.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ const formatTestNames: Readonly<Record<
8080
};
8181

8282
const REPLACE_REGEX = /%/g;
83-
const IGNORED_REGEX = /^.(?!gnored)/; // negative lookahead to not match `[iI]gnored`
83+
// filter to not match `[iI]gnored`
84+
const IGNORED_FILTER = {
85+
match: false,
86+
regex: /.gnored/.source,
87+
};
8488

8589
type Cases = {
8690
code: string[];
@@ -100,7 +104,7 @@ function createValidTestCases(cases: Cases): TSESLint.ValidTestCase<Options>[] {
100104
options: [
101105
{
102106
...options,
103-
filter: IGNORED_REGEX.source,
107+
filter: IGNORED_FILTER,
104108
},
105109
],
106110
code: `// ${JSON.stringify(options)}\n${test.code
@@ -206,7 +210,7 @@ function createInvalidTestCases(
206210
options: [
207211
{
208212
...options,
209-
filter: IGNORED_REGEX.source,
213+
filter: IGNORED_FILTER,
210214
},
211215
],
212216
code: `// ${JSON.stringify(options)}\n${test.code
@@ -606,6 +610,22 @@ ruleTester.run('naming-convention', rule, {
606610
valid: [
607611
`const x = 1;`, // no options shouldn't crash
608612
...createValidTestCases(cases),
613+
{
614+
code: `
615+
const child_process = require('child_process');
616+
`,
617+
parserOptions,
618+
options: [
619+
{
620+
selector: 'default',
621+
format: ['camelCase'],
622+
filter: {
623+
regex: 'child_process',
624+
match: false,
625+
},
626+
},
627+
],
628+
},
609629
{
610630
code: `
611631
declare const string_camelCase: string;
@@ -742,6 +762,23 @@ ruleTester.run('naming-convention', rule, {
742762
],
743763
invalid: [
744764
...createInvalidTestCases(cases),
765+
{
766+
code: `
767+
const child_process = require('child_process');
768+
`,
769+
parserOptions,
770+
options: [
771+
{
772+
selector: 'default',
773+
format: ['camelCase'],
774+
filter: {
775+
regex: 'child_process',
776+
match: true,
777+
},
778+
},
779+
],
780+
errors: [{ messageId: 'doesNotMatchFormat' }],
781+
},
745782
{
746783
code: `
747784
declare const string_camelCase01: string;

0 commit comments

Comments
 (0)
0