Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6a06944

Browse files
authored
feat(eslint-plugin): [naming-convention] add modifier unused (typescript-eslint#2810)
1 parent fa68492 commit 6a06944

File tree

3 files changed

+167
-26
lines changed

3 files changed

+167
-26
lines changed

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ If these are provided, the identifier must start with one of the provided values
167167
- Note that this does not match renamed destructured properties (`const {x: y, a: b = 2}`).
168168
- `global` - matches a variable/function declared in the top-level scope.
169169
- `exported` - matches anything that is exported from the module.
170+
- `unused` - matches anything that is not used.
170171
- `public` - matches any member that is either explicitly declared as `public`, or has no visibility modifier (i.e. implicitly public).
171172
- `readonly`, `static`, `abstract`, `protected`, `private` - matches any member explicitly declared with the given modifier.
172173
- `types` allows you to specify which types to match. This option supports simple, primitive types only (`boolean`, `string`, `number`, `array`, `function`).
@@ -204,13 +205,13 @@ There are two types of selectors, individual selectors, and grouped selectors.
204205
Individual Selectors match specific, well-defined sets. There is no overlap between each of the individual selectors.
205206

206207
- `variable` - matches any `var` / `let` / `const` variable name.
207-
- Allowed `modifiers`: `const`, `destructured`, `global`, `exported`.
208+
- Allowed `modifiers`: `const`, `destructured`, `global`, `exported`, `unused`.
208209
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
209210
- `function` - matches any named function declaration or named function expression.
210-
- Allowed `modifiers`: `global`, `exported`.
211+
- Allowed `modifiers`: `global`, `exported`, `unused`.
211212
- Allowed `types`: none.
212213
- `parameter` - matches any function parameter. Does not match parameter properties.
213-
- Allowed `modifiers`: none.
214+
- Allowed `modifiers`: `unused`.
214215
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
215216
- `classProperty` - matches any class property. Does not match properties that have direct function expression or arrow function expression values.
216217
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
@@ -240,19 +241,19 @@ Individual Selectors match specific, well-defined sets. There is no overlap betw
240241
- Allowed `modifiers`: none.
241242
- Allowed `types`: none.
242243
- `class` - matches any class declaration.
243-
- Allowed `modifiers`: `abstract`, `exported`.
244+
- Allowed `modifiers`: `abstract`, `exported`, `unused`.
244245
- Allowed `types`: none.
245246
- `interface` - matches any interface declaration.
246-
- Allowed `modifiers`: `exported`.
247+
- Allowed `modifiers`: `exported`, `unused`.
247248
- Allowed `types`: none.
248249
- `typeAlias` - matches any type alias declaration.
249-
- Allowed `modifiers`: `exported`.
250+
- Allowed `modifiers`: `exported`, `unused`.
250251
- Allowed `types`: none.
251252
- `enum` - matches any enum declaration.
252-
- Allowed `modifiers`: `exported`.
253+
- Allowed `modifiers`: `exported`, `unused`.
253254
- Allowed `types`: none.
254255
- `typeParameter` - matches any generic type parameter declaration.
255-
- Allowed `modifiers`: none.
256+
- Allowed `modifiers`: `unused`.
256257
- Allowed `types`: none.
257258

258259
##### Group Selectors
@@ -263,13 +264,13 @@ Group Selectors are provided for convenience, and essentially bundle up sets of
263264
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
264265
- Allowed `types`: none.
265266
- `variableLike` - matches the same as `variable`, `function` and `parameter`.
266-
- Allowed `modifiers`: none.
267+
- Allowed `modifiers`: `unused`.
267268
- Allowed `types`: none.
268269
- `memberLike` - matches the same as `property`, `parameterProperty`, `method`, `accessor`, `enumMember`.
269270
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
270271
- Allowed `types`: none.
271272
- `typeLike` - matches the same as `class`, `interface`, `typeAlias`, `enum`, `typeParameter`.
272-
- Allowed `modifiers`: `abstract`.
273+
- Allowed `modifiers`: `abstract`, `unused`.
273274
- Allowed `types`: none.
274275
- `property` - matches the same as `classProperty`, `objectLiteralProperty`, `typeProperty`.
275276
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.

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

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ enum Modifiers {
113113
global = 1 << 8,
114114
// things that are exported
115115
exported = 1 << 9,
116+
// things that are unused
117+
unused = 1 << 10,
116118
}
117119
type ModifiersString = keyof typeof Modifiers;
118120

@@ -334,15 +336,16 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
334336
selectorsSchema(),
335337
...selectorSchema('default', false, util.getEnumNames(Modifiers)),
336338

337-
...selectorSchema('variableLike', false),
339+
...selectorSchema('variableLike', false, ['unused']),
338340
...selectorSchema('variable', true, [
339341
'const',
340342
'destructured',
341343
'global',
342344
'exported',
345+
'unused',
343346
]),
344-
...selectorSchema('function', false, ['global', 'exported']),
345-
...selectorSchema('parameter', true),
347+
...selectorSchema('function', false, ['global', 'exported', 'unused']),
348+
...selectorSchema('parameter', true, ['unused']),
346349

347350
...selectorSchema('memberLike', false, [
348351
'private',
@@ -428,12 +431,12 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
428431
]),
429432
...selectorSchema('enumMember', false),
430433

431-
...selectorSchema('typeLike', false, ['abstract', 'exported']),
432-
...selectorSchema('class', false, ['abstract', 'exported']),
433-
...selectorSchema('interface', false, ['exported']),
434-
...selectorSchema('typeAlias', false, ['exported']),
435-
...selectorSchema('enum', false, ['exported']),
436-
...selectorSchema('typeParameter', false),
434+
...selectorSchema('typeLike', false, ['abstract', 'exported', 'unused']),
435+
...selectorSchema('class', false, ['abstract', 'exported', 'unused']),
436+
...selectorSchema('interface', false, ['exported', 'unused']),
437+
...selectorSchema('typeAlias', false, ['exported', 'unused']),
438+
...selectorSchema('enum', false, ['exported', 'unused']),
439+
...selectorSchema('typeParameter', false, ['unused']),
437440
],
438441
},
439442
additionalItems: false,
@@ -558,6 +561,27 @@ export default util.createRule<Options, MessageIds>({
558561
return modifiers;
559562
}
560563

564+
const unusedVariables = util.collectUnusedVariables(context);
565+
function isUnused(
566+
name: string,
567+
initialScope: TSESLint.Scope.Scope | null = context.getScope(),
568+
): boolean {
569+
let variable: TSESLint.Scope.Variable | null = null;
570+
let scope: TSESLint.Scope.Scope | null = initialScope;
571+
while (scope) {
572+
variable = scope.set.get(name) ?? null;
573+
if (variable) {
574+
break;
575+
}
576+
scope = scope.upper;
577+
}
578+
if (!variable) {
579+
return false;
580+
}
581+
582+
return unusedVariables.has(variable);
583+
}
584+
561585
return {
562586
// #region variable
563587

@@ -574,13 +598,15 @@ export default util.createRule<Options, MessageIds>({
574598
if (parent.kind === 'const') {
575599
baseModifiers.add(Modifiers.const);
576600
}
601+
577602
if (isGlobal(context.getScope())) {
578603
baseModifiers.add(Modifiers.global);
579604
}
580605
}
581606

582607
identifiers.forEach(id => {
583608
const modifiers = new Set(baseModifiers);
609+
584610
if (
585611
// `const { x }`
586612
// does not match `const { x: y }`
@@ -599,6 +625,10 @@ export default util.createRule<Options, MessageIds>({
599625
modifiers.add(Modifiers.exported);
600626
}
601627

628+
if (isUnused(id.name)) {
629+
modifiers.add(Modifiers.unused);
630+
}
631+
602632
validator(id, modifiers);
603633
});
604634
},
@@ -621,13 +651,19 @@ export default util.createRule<Options, MessageIds>({
621651
const modifiers = new Set<Modifiers>();
622652
// functions create their own nested scope
623653
const scope = context.getScope().upper;
654+
624655
if (isGlobal(scope)) {
625656
modifiers.add(Modifiers.global);
626657
}
658+
627659
if (isExported(node, node.id.name, scope)) {
628660
modifiers.add(Modifiers.exported);
629661
}
630662

663+
if (isUnused(node.id.name, scope)) {
664+
modifiers.add(Modifiers.unused);
665+
}
666+
631667
validator(node.id, modifiers);
632668
},
633669

@@ -655,7 +691,13 @@ export default util.createRule<Options, MessageIds>({
655691
const identifiers = getIdentifiersFromPattern(param);
656692

657693
identifiers.forEach(i => {
658-
validator(i);
694+
const modifiers = new Set<Modifiers>();
695+
696+
if (isUnused(i.name)) {
697+
modifiers.add(Modifiers.unused);
698+
}
699+
700+
validator(i, modifiers);
659701
});
660702
});
661703
},
@@ -803,15 +845,21 @@ export default util.createRule<Options, MessageIds>({
803845
}
804846

805847
const modifiers = new Set<Modifiers>();
848+
// classes create their own nested scope
849+
const scope = context.getScope().upper;
850+
806851
if (node.abstract) {
807852
modifiers.add(Modifiers.abstract);
808853
}
809854

810-
// classes create their own nested scope
811-
if (isExported(node, id.name, context.getScope().upper)) {
855+
if (isExported(node, id.name, scope)) {
812856
modifiers.add(Modifiers.exported);
813857
}
814858

859+
if (isUnused(id.name, scope)) {
860+
modifiers.add(Modifiers.unused);
861+
}
862+
815863
validator(id, modifiers);
816864
},
817865

@@ -826,10 +874,16 @@ export default util.createRule<Options, MessageIds>({
826874
}
827875

828876
const modifiers = new Set<Modifiers>();
829-
if (isExported(node, node.id.name, context.getScope())) {
877+
const scope = context.getScope();
878+
879+
if (isExported(node, node.id.name, scope)) {
830880
modifiers.add(Modifiers.exported);
831881
}
832882

883+
if (isUnused(node.id.name, scope)) {
884+
modifiers.add(Modifiers.unused);
885+
}
886+
833887
validator(node.id, modifiers);
834888
},
835889

@@ -844,10 +898,16 @@ export default util.createRule<Options, MessageIds>({
844898
}
845899

846900
const modifiers = new Set<Modifiers>();
847-
if (isExported(node, node.id.name, context.getScope())) {
901+
const scope = context.getScope();
902+
903+
if (isExported(node, node.id.name, scope)) {
848904
modifiers.add(Modifiers.exported);
849905
}
850906

907+
if (isUnused(node.id.name, scope)) {
908+
modifiers.add(Modifiers.unused);
909+
}
910+
851911
validator(node.id, modifiers);
852912
},
853913

@@ -863,10 +923,16 @@ export default util.createRule<Options, MessageIds>({
863923

864924
const modifiers = new Set<Modifiers>();
865925
// enums create their own nested scope
866-
if (isExported(node, node.id.name, context.getScope().upper)) {
926+
const scope = context.getScope().upper;
927+
928+
if (isExported(node, node.id.name, scope)) {
867929
modifiers.add(Modifiers.exported);
868930
}
869931

932+
if (isUnused(node.id.name, scope)) {
933+
modifiers.add(Modifiers.unused);
934+
}
935+
870936
validator(node.id, modifiers);
871937
},
872938

@@ -882,7 +948,14 @@ export default util.createRule<Options, MessageIds>({
882948
return;
883949
}
884950

885-
validator(node.name);
951+
const modifiers = new Set<Modifiers>();
952+
const scope = context.getScope();
953+
954+
if (isUnused(node.name.name, scope)) {
955+
modifiers.add(Modifiers.unused);
956+
}
957+
958+
validator(node.name, modifiers);
886959
},
887960

888961
// #endregion typeParameter

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,46 @@ ruleTester.run('naming-convention', rule, {
11751175
},
11761176
],
11771177
},
1178+
{
1179+
code: `
1180+
const UnusedVar = 1;
1181+
function UnusedFunc(
1182+
// this line is intentionally broken out
1183+
UnusedParam: string,
1184+
) {}
1185+
class UnusedClass {}
1186+
interface UnusedInterface {}
1187+
type UnusedType<
1188+
// this line is intentionally broken out
1189+
UnusedTypeParam
1190+
> = {};
1191+
1192+
export const used_var = 1;
1193+
export function used_func(
1194+
// this line is intentionally broken out
1195+
used_param: string,
1196+
) {
1197+
return used_param;
1198+
}
1199+
export class used_class {}
1200+
export interface used_interface {}
1201+
export type used_type<
1202+
// this line is intentionally broken out
1203+
used_typeparam
1204+
> = used_typeparam;
1205+
`,
1206+
options: [
1207+
{
1208+
selector: 'default',
1209+
format: ['snake_case'],
1210+
},
1211+
{
1212+
selector: 'default',
1213+
modifiers: ['unused'],
1214+
format: ['PascalCase'],
1215+
},
1216+
],
1217+
},
11781218
],
11791219
invalid: [
11801220
{
@@ -1823,5 +1863,32 @@ ruleTester.run('naming-convention', rule, {
18231863
],
18241864
errors: [{ messageId: 'doesNotMatchFormat' }],
18251865
},
1866+
{
1867+
code: `
1868+
const UnusedVar = 1;
1869+
function UnusedFunc(
1870+
// this line is intentionally broken out
1871+
UnusedParam: string,
1872+
) {}
1873+
class UnusedClass {}
1874+
interface UnusedInterface {}
1875+
type UnusedType<
1876+
// this line is intentionally broken out
1877+
UnusedTypeParam
1878+
> = {};
1879+
`,
1880+
options: [
1881+
{
1882+
selector: 'default',
1883+
format: ['PascalCase'],
1884+
},
1885+
{
1886+
selector: 'default',
1887+
modifiers: ['unused'],
1888+
format: ['snake_case'],
1889+
},
1890+
],
1891+
errors: Array(7).fill({ messageId: 'doesNotMatchFormat' }),
1892+
},
18261893
],
18271894
});

0 commit comments

Comments
 (0)