Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): [naming-convention] add modifier unused #2810

Merged
merged 1 commit into from Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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' }),
},
],
});