Skip to content

Commit

Permalink
feat(eslint-plugin): [naming-convention] add modifier unused (#2810)
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed Nov 24, 2020
1 parent fa68492 commit 6a06944
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 26 deletions.
21 changes: 11 additions & 10 deletions packages/eslint-plugin/docs/rules/naming-convention.md
Expand Up @@ -167,6 +167,7 @@ If these are provided, the identifier must start with one of the provided values
- Note that this does not match renamed destructured properties (`const {x: y, a: b = 2}`).
- `global` - matches a variable/function declared in the top-level scope.
- `exported` - matches anything that is exported from the module.
- `unused` - matches anything that is not used.
- `public` - matches any member that is either explicitly declared as `public`, or has no visibility modifier (i.e. implicitly public).
- `readonly`, `static`, `abstract`, `protected`, `private` - matches any member explicitly declared with the given modifier.
- `types` allows you to specify which types to match. This option supports simple, primitive types only (`boolean`, `string`, `number`, `array`, `function`).
Expand Down Expand Up @@ -204,13 +205,13 @@ There are two types of selectors, individual selectors, and grouped selectors.
Individual Selectors match specific, well-defined sets. There is no overlap between each of the individual selectors.

- `variable` - matches any `var` / `let` / `const` variable name.
- Allowed `modifiers`: `const`, `destructured`, `global`, `exported`.
- Allowed `modifiers`: `const`, `destructured`, `global`, `exported`, `unused`.
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
- `function` - matches any named function declaration or named function expression.
- Allowed `modifiers`: `global`, `exported`.
- Allowed `modifiers`: `global`, `exported`, `unused`.
- Allowed `types`: none.
- `parameter` - matches any function parameter. Does not match parameter properties.
- Allowed `modifiers`: none.
- Allowed `modifiers`: `unused`.
- Allowed `types`: `boolean`, `string`, `number`, `function`, `array`.
- `classProperty` - matches any class property. Does not match properties that have direct function expression or arrow function expression values.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
Expand Down Expand Up @@ -240,19 +241,19 @@ Individual Selectors match specific, well-defined sets. There is no overlap betw
- Allowed `modifiers`: none.
- Allowed `types`: none.
- `class` - matches any class declaration.
- Allowed `modifiers`: `abstract`, `exported`.
- Allowed `modifiers`: `abstract`, `exported`, `unused`.
- Allowed `types`: none.
- `interface` - matches any interface declaration.
- Allowed `modifiers`: `exported`.
- Allowed `modifiers`: `exported`, `unused`.
- Allowed `types`: none.
- `typeAlias` - matches any type alias declaration.
- Allowed `modifiers`: `exported`.
- Allowed `modifiers`: `exported`, `unused`.
- Allowed `types`: none.
- `enum` - matches any enum declaration.
- Allowed `modifiers`: `exported`.
- Allowed `modifiers`: `exported`, `unused`.
- Allowed `types`: none.
- `typeParameter` - matches any generic type parameter declaration.
- Allowed `modifiers`: none.
- Allowed `modifiers`: `unused`.
- Allowed `types`: none.

##### Group Selectors
Expand All @@ -263,13 +264,13 @@ Group Selectors are provided for convenience, and essentially bundle up sets of
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
- Allowed `types`: none.
- `variableLike` - matches the same as `variable`, `function` and `parameter`.
- Allowed `modifiers`: none.
- Allowed `modifiers`: `unused`.
- Allowed `types`: none.
- `memberLike` - matches the same as `property`, `parameterProperty`, `method`, `accessor`, `enumMember`.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
- Allowed `types`: none.
- `typeLike` - matches the same as `class`, `interface`, `typeAlias`, `enum`, `typeParameter`.
- Allowed `modifiers`: `abstract`.
- Allowed `modifiers`: `abstract`, `unused`.
- Allowed `types`: none.
- `property` - matches the same as `classProperty`, `objectLiteralProperty`, `typeProperty`.
- Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`.
Expand Down
105 changes: 89 additions & 16 deletions packages/eslint-plugin/src/rules/naming-convention.ts
Expand Up @@ -113,6 +113,8 @@ enum Modifiers {
global = 1 << 8,
// things that are exported
exported = 1 << 9,
// things that are unused
unused = 1 << 10,
}
type ModifiersString = keyof typeof Modifiers;

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

...selectorSchema('variableLike', false),
...selectorSchema('variableLike', false, ['unused']),
...selectorSchema('variable', true, [
'const',
'destructured',
'global',
'exported',
'unused',
]),
...selectorSchema('function', false, ['global', 'exported']),
...selectorSchema('parameter', true),
...selectorSchema('function', false, ['global', 'exported', 'unused']),
...selectorSchema('parameter', true, ['unused']),

...selectorSchema('memberLike', false, [
'private',
Expand Down Expand Up @@ -428,12 +431,12 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
]),
...selectorSchema('enumMember', false),

...selectorSchema('typeLike', false, ['abstract', 'exported']),
...selectorSchema('class', false, ['abstract', 'exported']),
...selectorSchema('interface', false, ['exported']),
...selectorSchema('typeAlias', false, ['exported']),
...selectorSchema('enum', false, ['exported']),
...selectorSchema('typeParameter', false),
...selectorSchema('typeLike', false, ['abstract', 'exported', 'unused']),
...selectorSchema('class', false, ['abstract', 'exported', 'unused']),
...selectorSchema('interface', false, ['exported', 'unused']),
...selectorSchema('typeAlias', false, ['exported', 'unused']),
...selectorSchema('enum', false, ['exported', 'unused']),
...selectorSchema('typeParameter', false, ['unused']),
],
},
additionalItems: false,
Expand Down Expand Up @@ -558,6 +561,27 @@ export default util.createRule<Options, MessageIds>({
return modifiers;
}

const unusedVariables = util.collectUnusedVariables(context);
function isUnused(
name: string,
initialScope: TSESLint.Scope.Scope | null = context.getScope(),
): boolean {
let variable: TSESLint.Scope.Variable | null = null;
let scope: TSESLint.Scope.Scope | null = initialScope;
while (scope) {
variable = scope.set.get(name) ?? null;
if (variable) {
break;
}
scope = scope.upper;
}
if (!variable) {
return false;
}

return unusedVariables.has(variable);
}

return {
// #region variable

Expand All @@ -574,13 +598,15 @@ export default util.createRule<Options, MessageIds>({
if (parent.kind === 'const') {
baseModifiers.add(Modifiers.const);
}

if (isGlobal(context.getScope())) {
baseModifiers.add(Modifiers.global);
}
}

identifiers.forEach(id => {
const modifiers = new Set(baseModifiers);

if (
// `const { x }`
// does not match `const { x: y }`
Expand All @@ -599,6 +625,10 @@ export default util.createRule<Options, MessageIds>({
modifiers.add(Modifiers.exported);
}

if (isUnused(id.name)) {
modifiers.add(Modifiers.unused);
}

validator(id, modifiers);
});
},
Expand All @@ -621,13 +651,19 @@ export default util.createRule<Options, MessageIds>({
const modifiers = new Set<Modifiers>();
// functions create their own nested scope
const scope = context.getScope().upper;

if (isGlobal(scope)) {
modifiers.add(Modifiers.global);
}

if (isExported(node, node.id.name, scope)) {
modifiers.add(Modifiers.exported);
}

if (isUnused(node.id.name, scope)) {
modifiers.add(Modifiers.unused);
}

validator(node.id, modifiers);
},

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

identifiers.forEach(i => {
validator(i);
const modifiers = new Set<Modifiers>();

if (isUnused(i.name)) {
modifiers.add(Modifiers.unused);
}

validator(i, modifiers);
});
});
},
Expand Down Expand Up @@ -803,15 +845,21 @@ export default util.createRule<Options, MessageIds>({
}

const modifiers = new Set<Modifiers>();
// classes create their own nested scope
const scope = context.getScope().upper;

if (node.abstract) {
modifiers.add(Modifiers.abstract);
}

// classes create their own nested scope
if (isExported(node, id.name, context.getScope().upper)) {
if (isExported(node, id.name, scope)) {
modifiers.add(Modifiers.exported);
}

if (isUnused(id.name, scope)) {
modifiers.add(Modifiers.unused);
}

validator(id, modifiers);
},

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

const modifiers = new Set<Modifiers>();
if (isExported(node, node.id.name, context.getScope())) {
const scope = context.getScope();

if (isExported(node, node.id.name, scope)) {
modifiers.add(Modifiers.exported);
}

if (isUnused(node.id.name, scope)) {
modifiers.add(Modifiers.unused);
}

validator(node.id, modifiers);
},

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

const modifiers = new Set<Modifiers>();
if (isExported(node, node.id.name, context.getScope())) {
const scope = context.getScope();

if (isExported(node, node.id.name, scope)) {
modifiers.add(Modifiers.exported);
}

if (isUnused(node.id.name, scope)) {
modifiers.add(Modifiers.unused);
}

validator(node.id, modifiers);
},

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

const modifiers = new Set<Modifiers>();
// enums create their own nested scope
if (isExported(node, node.id.name, context.getScope().upper)) {
const scope = context.getScope().upper;

if (isExported(node, node.id.name, scope)) {
modifiers.add(Modifiers.exported);
}

if (isUnused(node.id.name, scope)) {
modifiers.add(Modifiers.unused);
}

validator(node.id, modifiers);
},

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

validator(node.name);
const modifiers = new Set<Modifiers>();
const scope = context.getScope();

if (isUnused(node.name.name, scope)) {
modifiers.add(Modifiers.unused);
}

validator(node.name, modifiers);
},

// #endregion typeParameter
Expand Down
67 changes: 67 additions & 0 deletions packages/eslint-plugin/tests/rules/naming-convention.test.ts
Expand Up @@ -1175,6 +1175,46 @@ ruleTester.run('naming-convention', rule, {
},
],
},
{
code: `
const UnusedVar = 1;
function UnusedFunc(
// this line is intentionally broken out
UnusedParam: string,
) {}
class UnusedClass {}
interface UnusedInterface {}
type UnusedType<
// this line is intentionally broken out
UnusedTypeParam
> = {};
export const used_var = 1;
export function used_func(
// this line is intentionally broken out
used_param: string,
) {
return used_param;
}
export class used_class {}
export interface used_interface {}
export type used_type<
// this line is intentionally broken out
used_typeparam
> = used_typeparam;
`,
options: [
{
selector: 'default',
format: ['snake_case'],
},
{
selector: 'default',
modifiers: ['unused'],
format: ['PascalCase'],
},
],
},
],
invalid: [
{
Expand Down Expand Up @@ -1823,5 +1863,32 @@ ruleTester.run('naming-convention', rule, {
],
errors: [{ messageId: 'doesNotMatchFormat' }],
},
{
code: `
const UnusedVar = 1;
function UnusedFunc(
// this line is intentionally broken out
UnusedParam: string,
) {}
class UnusedClass {}
interface UnusedInterface {}
type UnusedType<
// this line is intentionally broken out
UnusedTypeParam
> = {};
`,
options: [
{
selector: 'default',
format: ['PascalCase'],
},
{
selector: 'default',
modifiers: ['unused'],
format: ['snake_case'],
},
],
errors: Array(7).fill({ messageId: 'doesNotMatchFormat' }),
},
],
});

0 comments on commit 6a06944

Please sign in to comment.