diff --git a/packages/eslint-plugin/docs/rules/naming-convention.md b/packages/eslint-plugin/docs/rules/naming-convention.md index d57bc1c69daf..06808dfff3f1 100644 --- a/packages/eslint-plugin/docs/rules/naming-convention.md +++ b/packages/eslint-plugin/docs/rules/naming-convention.md @@ -154,21 +154,30 @@ If these are provided, the identifier must start with one of the provided values ### Selector Options -- `selector` (see "Allowed Selectors, Modifiers and Types" below). +- `selector` allows you to specify what types of identifiers to target. - Accepts one or array of selectors to define an option block that applies to one or multiple selectors. - For example, if you provide `{ selector: ['variable', 'function'] }`, then it will apply the same option to variable and function nodes. + - See [Allowed Selectors, Modifiers and Types](#allowed-selectors-modifiers-and-types) below for the complete list of allowed selectors. - `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. + - The following `modifiers` are allowed: + - `const` - matches a variable declared as being `const` (`const x = 1`). + - `destructured` - matches a variable declared via an object destructuring pattern (`const {x, ignored: y, z = 2}`). + - `global` - matches a variable/function declared in the top-level scope. + - `exported` - matches anything that is exported from the module. + - `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`). - The name must match _one_ of the types. - **_NOTE - Using this option will require that you lint with type information._** - For example, this lets you do things like enforce that `boolean` variables are prefixed with a verb. - - `boolean` matches any type assignable to `boolean | null | undefined` - - `string` matches any type assignable to `string | null | undefined` - - `number` matches any type assignable to `number | null | undefined` - - `array` matches any type assignable to `Array | null | undefined` - - `function` matches any type assignable to `Function | null | undefined` + - The following `types` are allowed: + - `boolean` matches any type assignable to `boolean | null | undefined` + - `string` matches any type assignable to `string | null | undefined` + - `number` matches any type assignable to `number | null | undefined` + - `array` matches any type assignable to `Array | null | undefined` + - `function` matches any type assignable to `Function | null | undefined` The ordering of selectors does not matter. The implementation will automatically sort the selectors to ensure they match from most-specific to least specific. It will keep checking selectors in that order until it finds one that matches the name. @@ -194,21 +203,33 @@ 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`. + - Allowed `modifiers`: `const`, `destructured`, `global`, `exported`. - Allowed `types`: `boolean`, `string`, `number`, `function`, `array`. - `function` - matches any named function declaration or named function expression. - - Allowed `modifiers`: none. + - Allowed `modifiers`: `global`, `exported`. - Allowed `types`: none. - `parameter` - matches any function parameter. Does not match parameter properties. - Allowed `modifiers`: none. - Allowed `types`: `boolean`, `string`, `number`, `function`, `array`. -- `property` - matches any object, class, or object type property. Does not match properties that have direct function expression or arrow function expression values. +- `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`. + - Allowed `types`: `boolean`, `string`, `number`, `function`, `array`. +- `objectLiteralProperty` - matches any object literal property. Does not match properties that have direct function expression or arrow function expression values. + - Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`. + - Allowed `types`: `boolean`, `string`, `number`, `function`, `array`. +- `typeProperty` - matches any object type property. Does not match properties that have direct function expression or arrow function expression values. - Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`. - Allowed `types`: `boolean`, `string`, `number`, `function`, `array`. - `parameterProperty` - matches any parameter property. - Allowed `modifiers`: `private`, `protected`, `public`, `readonly`. - Allowed `types`: `boolean`, `string`, `number`, `function`, `array`. -- `method` - matches any object, class, or object type method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors. +- `classMethod` - matches any class method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors. + - Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`. + - Allowed `types`: none. +- `objectLiteralMethod` - matches any object literal method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors. + - Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`. + - Allowed `types`: none. +- `typeMethod` - matches any object type method. Also matches properties that have direct function expression or arrow function expression values. Does not match accessors. - Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`. - Allowed `types`: none. - `accessor` - matches any accessor. @@ -218,16 +239,16 @@ 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`. + - Allowed `modifiers`: `abstract`, `exported`. - Allowed `types`: none. - `interface` - matches any interface declaration. - - Allowed `modifiers`: none. + - Allowed `modifiers`: `exported`. - Allowed `types`: none. - `typeAlias` - matches any type alias declaration. - - Allowed `modifiers`: none. + - Allowed `modifiers`: `exported`. - Allowed `types`: none. - `enum` - matches any enum declaration. - - Allowed `modifiers`: none. + - Allowed `modifiers`: `exported`. - Allowed `types`: none. - `typeParameter` - matches any generic type parameter declaration. - Allowed `modifiers`: none. @@ -243,11 +264,17 @@ Group Selectors are provided for convenience, and essentially bundle up sets of - `variableLike` - matches the same as `variable`, `function` and `parameter`. - Allowed `modifiers`: none. - Allowed `types`: none. -- `memberLike` - matches the same as `property`, `parameterProperty`, `method`, `accessor`, `enumMember`. +- `memberLike` - matches the same as `classProperty`, `objectLiteralProperty`, `typeProperty`, `parameterProperty`, `classMethod`, `objectLiteralMethod`, `typeMethod`, `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`, `exported`. + - Allowed `types`: none. +- `property` - matches the same as `classProperty`, `objectLiteralProperty`, `typeProperty`. + - Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`. + - Allowed `types`: `boolean`, `string`, `number`, `function`, `array`. +- `method` - matches the same as `classMethod`, `objectLiteralMethod`, `typeMethod`. + - Allowed `modifiers`: `private`, `protected`, `public`, `static`, `readonly`, `abstract`. - Allowed `types`: none. ## Examples @@ -423,6 +450,25 @@ You can use the `filter` option to ignore names that require quoting: } ``` +### Ignore destructured names + +Sometimes you might want to allow destructured properties to retain their original name, even if it breaks your naming convention. + +You can use the `destructured` modifier to match these names, and explicitly set `format: null` to apply no formatting: + +```jsonc +{ + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "variable", + "modifiers": ["destructured"], + "format": null + } + ] +} +``` + ### Enforce the codebase follows ESLint's `camelcase` conventions ```json diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index 295e8209e89b..229463ec6d3e 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -4,6 +4,7 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; +import { PatternVisitor } from '@typescript-eslint/scope-manager'; import * as ts from 'typescript'; import * as util from '../util'; @@ -41,21 +42,24 @@ enum Selectors { parameter = 1 << 2, // memberLike - property = 1 << 3, - parameterProperty = 1 << 4, - method = 1 << 5, - accessor = 1 << 6, - enumMember = 1 << 7, + parameterProperty = 1 << 3, + accessor = 1 << 4, + enumMember = 1 << 5, + classMethod = 1 << 6, + objectLiteralMethod = 1 << 7, + typeMethod = 1 << 8, + classProperty = 1 << 9, + objectLiteralProperty = 1 << 10, + typeProperty = 1 << 11, // typeLike - class = 1 << 8, - interface = 1 << 9, - typeAlias = 1 << 10, - enum = 1 << 11, - typeParameter = 1 << 12, + class = 1 << 12, + interface = 1 << 13, + typeAlias = 1 << 14, + enum = 1 << 15, + typeParameter = 1 << 17, } type SelectorsString = keyof typeof Selectors; -const SELECTOR_COUNT = util.getEnumNames(Selectors).length; enum MetaSelectors { default = -1, @@ -64,10 +68,14 @@ enum MetaSelectors { Selectors.function | Selectors.parameter, memberLike = 0 | - Selectors.property | + Selectors.classProperty | + Selectors.objectLiteralProperty | + Selectors.typeProperty | Selectors.parameterProperty | Selectors.enumMember | - Selectors.method | + Selectors.classMethod | + Selectors.objectLiteralMethod | + Selectors.typeMethod | Selectors.accessor, typeLike = 0 | Selectors.class | @@ -75,18 +83,36 @@ enum MetaSelectors { Selectors.typeAlias | Selectors.enum | Selectors.typeParameter, + method = 0 | + Selectors.classMethod | + Selectors.objectLiteralMethod | + Selectors.typeProperty, + property = 0 | + Selectors.classProperty | + Selectors.objectLiteralProperty | + Selectors.typeMethod, } type MetaSelectorsString = keyof typeof MetaSelectors; type IndividualAndMetaSelectorsString = SelectorsString | MetaSelectorsString; enum Modifiers { + // const variable const = 1 << 0, + // readonly members readonly = 1 << 1, + // static members static = 1 << 2, + // member accessibility public = 1 << 3, protected = 1 << 4, private = 1 << 5, abstract = 1 << 6, + // destructured variable + destructured = 1 << 7, + // variables declared in the top-level scope + global = 1 << 8, + // things that are exported + exported = 1 << 9, } type ModifiersString = keyof typeof Modifiers; @@ -309,8 +335,13 @@ const SCHEMA: JSONSchema.JSONSchema4 = { ...selectorSchema('default', false, util.getEnumNames(Modifiers)), ...selectorSchema('variableLike', false), - ...selectorSchema('variable', true, ['const']), - ...selectorSchema('function', false), + ...selectorSchema('variable', true, [ + 'const', + 'destructured', + 'global', + 'exported', + ]), + ...selectorSchema('function', false, ['global', 'exported']), ...selectorSchema('parameter', true), ...selectorSchema('memberLike', false, [ @@ -321,7 +352,23 @@ const SCHEMA: JSONSchema.JSONSchema4 = { 'readonly', 'abstract', ]), - ...selectorSchema('property', true, [ + ...selectorSchema('classProperty', true, [ + 'private', + 'protected', + 'public', + 'static', + 'readonly', + 'abstract', + ]), + ...selectorSchema('objectLiteralProperty', true, [ + 'private', + 'protected', + 'public', + 'static', + 'readonly', + 'abstract', + ]), + ...selectorSchema('typeProperty', true, [ 'private', 'protected', 'public', @@ -335,6 +382,36 @@ const SCHEMA: JSONSchema.JSONSchema4 = { 'public', 'readonly', ]), + ...selectorSchema('property', true, [ + 'private', + 'protected', + 'public', + 'static', + 'readonly', + 'abstract', + ]), + + ...selectorSchema('classMethod', false, [ + 'private', + 'protected', + 'public', + 'static', + 'abstract', + ]), + ...selectorSchema('objectLiteralMethod', false, [ + 'private', + 'protected', + 'public', + 'static', + 'abstract', + ]), + ...selectorSchema('typeMethod', false, [ + 'private', + 'protected', + 'public', + 'static', + 'abstract', + ]), ...selectorSchema('method', false, [ 'private', 'protected', @@ -342,6 +419,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = { 'static', 'abstract', ]), + ...selectorSchema('accessor', true, [ 'private', 'protected', @@ -351,11 +429,11 @@ const SCHEMA: JSONSchema.JSONSchema4 = { ]), ...selectorSchema('enumMember', false), - ...selectorSchema('typeLike', false, ['abstract']), - ...selectorSchema('class', false, ['abstract']), - ...selectorSchema('interface', false), - ...selectorSchema('typeAlias', false), - ...selectorSchema('enum', 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), ], }, @@ -490,21 +568,40 @@ export default util.createRule({ return; } - const identifiers: TSESTree.Identifier[] = []; - getIdentifiersFromPattern(node.id, identifiers); + const identifiers = getIdentifiersFromPattern(node.id); - const modifiers = new Set(); + const baseModifiers = new Set(); const parent = node.parent; - if ( - parent && - parent.type === AST_NODE_TYPES.VariableDeclaration && - parent.kind === 'const' - ) { - modifiers.add(Modifiers.const); + if (parent?.type === AST_NODE_TYPES.VariableDeclaration) { + if (parent.kind === 'const') { + baseModifiers.add(Modifiers.const); + } + if (isGlobal(context.getScope())) { + baseModifiers.add(Modifiers.global); + } } - identifiers.forEach(i => { - validator(i, modifiers); + identifiers.forEach(id => { + const modifiers = new Set(baseModifiers); + if ( + // `const { x }` + // does not match `const { x: y }` + (id.parent?.type === AST_NODE_TYPES.Property && + id.parent.shorthand) || + // `const { x = 2 }` + // does not match const `{ x: y = 2 }` + (id.parent?.type === AST_NODE_TYPES.AssignmentPattern && + id.parent.parent?.type === AST_NODE_TYPES.Property && + id.parent.parent.shorthand) + ) { + modifiers.add(Modifiers.destructured); + } + + if (isExported(parent, id.name, context.getScope())) { + modifiers.add(Modifiers.exported); + } + + validator(id, modifiers); }); }, @@ -523,7 +620,17 @@ export default util.createRule({ return; } - validator(node.id); + const modifiers = new Set(); + const scope = context.getScope().upper; + // functions will + if (isGlobal(scope)) { + modifiers.add(Modifiers.global); + } + if (isExported(node, node.id.name, scope)) { + modifiers.add(Modifiers.exported); + } + + validator(node.id, modifiers); }, // #endregion function @@ -547,8 +654,7 @@ export default util.createRule({ return; } - const identifiers: TSESTree.Identifier[] = []; - getIdentifiersFromPattern(param, identifiers); + const identifiers = getIdentifiersFromPattern(param); identifiers.forEach(i => { validator(i); @@ -568,8 +674,7 @@ export default util.createRule({ const modifiers = getMemberModifiers(node); - const identifiers: TSESTree.Identifier[] = []; - getIdentifiersFromPattern(node.parameter, identifiers); + const identifiers = getIdentifiersFromPattern(node.parameter); identifiers.forEach(i => { validator(i, modifiers); @@ -584,7 +689,7 @@ export default util.createRule({ node: TSESTree.PropertyNonComputedName, ): void { const modifiers = new Set([Modifiers.public]); - handleMember(validators.property, node, modifiers); + handleMember(validators.objectLiteralProperty, node, modifiers); }, ':matches(ClassProperty, TSAbstractClassProperty)[computed = false][value.type != "ArrowFunctionExpression"][value.type != "FunctionExpression"][value.type != "TSEmptyBodyFunctionExpression"]'( @@ -593,7 +698,7 @@ export default util.createRule({ | TSESTree.TSAbstractClassPropertyNonComputedName, ): void { const modifiers = getMemberModifiers(node); - handleMember(validators.property, node, modifiers); + handleMember(validators.classProperty, node, modifiers); }, 'TSPropertySignature[computed = false]'( @@ -604,7 +709,7 @@ export default util.createRule({ modifiers.add(Modifiers.readonly); } - handleMember(validators.property, node, modifiers); + handleMember(validators.typeProperty, node, modifiers); }, // #endregion property @@ -615,14 +720,20 @@ export default util.createRule({ 'Property[computed = false][kind = "init"][value.type = "ArrowFunctionExpression"]', 'Property[computed = false][kind = "init"][value.type = "FunctionExpression"]', 'Property[computed = false][kind = "init"][value.type = "TSEmptyBodyFunctionExpression"]', - 'TSMethodSignature[computed = false]', ].join(', ')]( node: | TSESTree.PropertyNonComputedName | TSESTree.TSMethodSignatureNonComputedName, ): void { const modifiers = new Set([Modifiers.public]); - handleMember(validators.method, node, modifiers); + handleMember(validators.objectLiteralMethod, node, modifiers); + }, + + 'TSMethodSignature[computed = false]'( + node: TSESTree.TSMethodSignatureNonComputedName, + ): void { + const modifiers = new Set([Modifiers.public]); + handleMember(validators.typeMethod, node, modifiers); }, [[ @@ -638,7 +749,7 @@ export default util.createRule({ | TSESTree.TSAbstractMethodDefinitionNonComputedName, ): void { const modifiers = getMemberModifiers(node); - handleMember(validators.method, node, modifiers); + handleMember(validators.classMethod, node, modifiers); }, // #endregion method @@ -698,6 +809,10 @@ export default util.createRule({ modifiers.add(Modifiers.abstract); } + if (isExported(node, id.name, context.getScope().upper)) { + modifiers.add(Modifiers.exported); + } + validator(id, modifiers); }, @@ -711,7 +826,12 @@ export default util.createRule({ return; } - validator(node.id); + const modifiers = new Set(); + if (isExported(node, node.id.name, context.getScope())) { + modifiers.add(Modifiers.exported); + } + + validator(node.id, modifiers); }, // #endregion interface @@ -724,7 +844,12 @@ export default util.createRule({ return; } - validator(node.id); + const modifiers = new Set(); + if (isExported(node, node.id.name, context.getScope())) { + modifiers.add(Modifiers.exported); + } + + validator(node.id, modifiers); }, // #endregion typeAlias @@ -737,7 +862,12 @@ export default util.createRule({ return; } - validator(node.id); + const modifiers = new Set(); + if (isExported(node, node.id.name, context.getScope().upper)) { + modifiers.add(Modifiers.exported); + } + + validator(node.id, modifiers); }, // #endregion enum @@ -762,55 +892,54 @@ export default util.createRule({ function getIdentifiersFromPattern( pattern: TSESTree.DestructuringPattern, - identifiers: TSESTree.Identifier[], -): void { - switch (pattern.type) { - case AST_NODE_TYPES.Identifier: - identifiers.push(pattern); - break; - - case AST_NODE_TYPES.ArrayPattern: - pattern.elements.forEach(element => { - if (element !== null) { - getIdentifiersFromPattern(element, identifiers); - } - }); - break; - - case AST_NODE_TYPES.ObjectPattern: - pattern.properties.forEach(property => { - if (property.type === AST_NODE_TYPES.RestElement) { - getIdentifiersFromPattern(property, identifiers); - } else { - // this is a bit weird, but it's because ESTree doesn't have a new node type - // for object destructuring properties - it just reuses Property... - // https://github.com/estree/estree/blob/9ae284b71130d53226e7153b42f01bf819e6e657/es2015.md#L206-L211 - // However, the parser guarantees this is safe (and there is error handling) - getIdentifiersFromPattern( - property.value as TSESTree.DestructuringPattern, - identifiers, - ); - } - }); - break; +): TSESTree.Identifier[] { + const identifiers: TSESTree.Identifier[] = []; + const visitor = new PatternVisitor({}, pattern, id => identifiers.push(id)); + visitor.visit(pattern); + return identifiers; +} - case AST_NODE_TYPES.RestElement: - getIdentifiersFromPattern(pattern.argument, identifiers); - break; +function isExported( + node: TSESTree.Node | undefined, + name: string, + scope: TSESLint.Scope.Scope | null, +): boolean { + if ( + node?.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || + node?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration + ) { + return true; + } - case AST_NODE_TYPES.AssignmentPattern: - getIdentifiersFromPattern(pattern.left, identifiers); - break; + if (scope == null) { + return false; + } - case AST_NODE_TYPES.MemberExpression: - // ignore member expressions, as the everything must already be defined - break; + const variable = scope.set.get(name); + if (variable) { + for (const ref of variable.references) { + const refParent = ref.identifier.parent; + if ( + refParent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || + refParent?.type === AST_NODE_TYPES.ExportSpecifier + ) { + return true; + } + } + } + + return false; +} - default: - // https://github.com/typescript-eslint/typescript-eslint/issues/1282 - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - throw new Error(`Unexpected pattern type ${pattern!.type}`); +function isGlobal(scope: TSESLint.Scope.Scope | null): boolean { + if (scope == null) { + return false; } + + return ( + scope.type === TSESLint.Scope.ScopeType.global || + scope.type === TSESLint.Scope.ScopeType.module + ); } type ValidatorFunction = ( @@ -851,21 +980,20 @@ function createValidator( return b.modifierWeight - a.modifierWeight; } - /* - meta selectors will always be larger numbers than the normal selectors they contain, as they are the sum of all - of the selectors that they contain. - to give normal selectors a higher priority, shift them all SELECTOR_COUNT bits to the left before comparison, so - they are instead always guaranteed to be larger than the meta selectors. - */ - const aSelector = isMetaSelector(a.selector) - ? a.selector - : a.selector << SELECTOR_COUNT; - const bSelector = isMetaSelector(b.selector) - ? b.selector - : b.selector << SELECTOR_COUNT; + const aIsMeta = isMetaSelector(a.selector); + const bIsMeta = isMetaSelector(b.selector); + // non-meta selectors should go ahead of meta selectors + if (aIsMeta && !bIsMeta) { + return 1; + } + if (!aIsMeta && bIsMeta) { + return -1; + } + + // both aren't meta selectors // sort descending - the meta selectors are "least important" - return bSelector - aSelector; + return b.selector - a.selector; }); return ( @@ -1314,13 +1442,14 @@ function normalizeOption(option: Selector): NormalizedSelector[] { ? option.selector : [option.selector]; - const selectorsAllowedToHaveTypes: (Selectors | MetaSelectors)[] = [ - Selectors.variable, - Selectors.parameter, - Selectors.property, - Selectors.parameterProperty, - Selectors.accessor, - ]; + const selectorsAllowedToHaveTypes = + Selectors.variable | + Selectors.parameter | + Selectors.classProperty | + Selectors.objectLiteralProperty | + Selectors.typeProperty | + Selectors.parameterProperty | + Selectors.accessor; const config: NormalizedSelector[] = []; selectors @@ -1328,7 +1457,7 @@ function normalizeOption(option: Selector): NormalizedSelector[] { isMetaSelector(selector) ? MetaSelectors[selector] : Selectors[selector], ) .forEach(selector => - selectorsAllowedToHaveTypes.includes(selector) + (selectorsAllowedToHaveTypes & selector) !== 0 ? config.push({ selector: selector, ...normalizedOption }) : config.push({ selector: selector, diff --git a/packages/eslint-plugin/tests/rules/naming-convention.test.ts b/packages/eslint-plugin/tests/rules/naming-convention.test.ts index c57dfc984d47..86df78496f9b 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention.test.ts @@ -214,7 +214,9 @@ function createInvalidTestCases( ...(selector !== 'default' && selector !== 'variableLike' && selector !== 'memberLike' && - selector !== 'typeLike' + selector !== 'typeLike' && + selector !== 'property' && + selector !== 'method' ? { data: { type: selectorTypeToMessageString(selector), @@ -444,12 +446,6 @@ const cases: Cases = [ // #region property { code: [ - 'const ignored = { % };', - 'const ignored = { "%": 1 };', - 'interface Ignored { % }', - 'interface Ignored { "%": string }', - 'type Ignored = { % }', - 'type Ignored = { "%": string }', 'class Ignored { private % }', 'class Ignored { private "%" = 1 }', 'class Ignored { private readonly % = 1 }', @@ -459,16 +455,24 @@ const cases: Cases = [ 'class Ignored { declare % }', ], options: { - selector: 'property', + selector: 'classProperty', + }, + }, + { + code: ['const ignored = { % };', 'const ignored = { "%": 1 };'], + options: { + selector: 'objectLiteralProperty', }, }, { code: [ - 'class Ignored { abstract private static readonly % = 1; ignoredDueToModifiers = 1; }', + 'interface Ignored { % }', + 'interface Ignored { "%": string }', + 'type Ignored = { % }', + 'type Ignored = { "%": string }', ], options: { - selector: 'property', - modifiers: ['static', 'readonly'], + selector: 'typeProperty', }, }, // #endregion property @@ -484,25 +488,11 @@ const cases: Cases = [ selector: 'parameterProperty', }, }, - { - code: ['class Ignored { constructor(private readonly %) {} }'], - options: { - selector: 'parameterProperty', - modifiers: ['readonly'], - }, - }, // #endregion parameterProperty // #region method { code: [ - 'const ignored = { %() {} };', - 'const ignored = { "%"() {} };', - 'const ignored = { %: () => {} };', - 'interface Ignored { %(): string }', - 'interface Ignored { "%"(): string }', - 'type Ignored = { %(): string }', - 'type Ignored = { "%"(): string }', 'class Ignored { private %() {} }', 'class Ignored { private "%"() {} }', 'class Ignored { private readonly %() {} }', @@ -513,16 +503,28 @@ const cases: Cases = [ 'class Ignored { declare %() }', ], options: { - selector: 'method', + selector: 'classMethod', }, }, { code: [ - 'class Ignored { abstract private static %() {}; ignoredDueToModifiers() {}; }', + 'const ignored = { %() {} };', + 'const ignored = { "%"() {} };', + 'const ignored = { %: () => {} };', + ], + options: { + selector: 'objectLiteralMethod', + }, + }, + { + code: [ + 'interface Ignored { %(): string }', + 'interface Ignored { "%"(): string }', + 'type Ignored = { %(): string }', + 'type Ignored = { "%"(): string }', ], options: { - selector: 'method', - modifiers: ['abstract', 'static'], + selector: 'typeMethod', }, }, // #endregion method @@ -540,15 +542,6 @@ const cases: Cases = [ selector: 'accessor', }, }, - { - code: [ - 'class Ignored { private static get %() {}; get ignoredDueToModifiers() {}; }', - ], - options: { - selector: 'accessor', - modifiers: ['private', 'static'], - }, - }, // #endregion accessor // #region enumMember @@ -567,13 +560,6 @@ const cases: Cases = [ selector: 'class', }, }, - { - code: ['abstract class % {}; class ignoredDueToModifier {}'], - options: { - selector: 'class', - modifiers: ['abstract'], - }, - }, // #endregion class // #region interface @@ -914,12 +900,280 @@ ruleTester.run('naming-convention', rule, { export const { OtherConstant: otherConstant } = SomeClass; `, - parserOptions, options: [ { selector: 'property', format: ['PascalCase'] }, { selector: 'variable', format: ['camelCase'] }, ], }, + { + code: ` + const camelCaseVar = 1; + enum camelCaseEnum {} + class camelCaseClass {} + function camelCaseFunction() {} + interface camelCaseInterface {} + type camelCaseType = {}; + + export const PascalCaseVar = 1; + export enum PascalCaseEnum {} + export class PascalCaseClass {} + export function PascalCaseFunction() {} + export interface PascalCaseInterface {} + export type PascalCaseType = {}; + `, + options: [ + { selector: 'default', format: ['camelCase'] }, + { + selector: 'variable', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'function', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'class', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'interface', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'typeAlias', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'enum', + format: ['PascalCase'], + modifiers: ['exported'], + }, + ], + }, + { + code: ` + const camelCaseVar = 1; + enum camelCaseEnum {} + class camelCaseClass {} + function camelCaseFunction() {} + interface camelCaseInterface {} + type camelCaseType = {}; + + const PascalCaseVar = 1; + enum PascalCaseEnum {} + class PascalCaseClass {} + function PascalCaseFunction() {} + interface PascalCaseInterface {} + type PascalCaseType = {}; + + export { + PascalCaseVar, + PascalCaseEnum, + PascalCaseClass, + PascalCaseFunction, + PascalCaseInterface, + PascalCaseType, + }; + `, + options: [ + { selector: 'default', format: ['camelCase'] }, + { + selector: 'variable', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'function', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'class', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'interface', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'typeAlias', + format: ['PascalCase'], + modifiers: ['exported'], + }, + { + selector: 'enum', + format: ['PascalCase'], + modifiers: ['exported'], + }, + ], + }, + { + code: ` + { + const camelCaseVar = 1; + function camelCaseFunction() {} + declare function camelCaseDeclaredFunction() { + }; + } + + const PascalCaseVar = 1; + function PascalCaseFunction() {} + declare function PascalCaseDeclaredFunction() { + }; + `, + options: [ + { selector: 'default', format: ['camelCase'] }, + { + selector: 'variable', + format: ['PascalCase'], + modifiers: ['global'], + }, + { + selector: 'function', + format: ['PascalCase'], + modifiers: ['global'], + }, + ], + }, + { + code: ` + const { some_name1 } = {}; + const { ignore: IgnoredDueToModifiers1 } = {}; + const { some_name2 = 2 } = {}; + + const IgnoredDueToModifiers2 = 1; + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'variable', + format: ['snake_case'], + modifiers: ['destructured'], + }, + ], + }, + { + code: ` + class Ignored { + private static abstract readonly some_name = 1; + IgnoredDueToModifiers = 1; + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'classProperty', + format: ['snake_case'], + modifiers: ['static', 'readonly'], + }, + ], + }, + { + code: ` + class Ignored { + constructor(private readonly some_name, IgnoredDueToModifiers) {} + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'parameterProperty', + format: ['snake_case'], + modifiers: ['readonly'], + }, + ], + }, + { + code: ` + class Ignored { + private static abstract some_name() {} + IgnoredDueToModifiers() {} + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'classMethod', + format: ['snake_case'], + modifiers: ['abstract', 'static'], + }, + ], + }, + { + code: ` + class Ignored { + private static get some_name() {} + get IgnoredDueToModifiers() {} + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'accessor', + format: ['snake_case'], + modifiers: ['private', 'static'], + }, + ], + }, + { + code: ` + abstract class some_name {} + class IgnoredDueToModifier {} + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'class', + format: ['snake_case'], + modifiers: ['abstract'], + }, + ], + }, + { + code: ` + const { some_name1 } = {}; + const { ignore: IgnoredDueToModifiers1 } = {}; + const { some_name2 = 2 } = {}; + + const IgnoredDueToModifiers2 = 1; + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'variable', + format: null, + modifiers: ['destructured'], + }, + ], + }, ], invalid: [ { @@ -1239,8 +1493,7 @@ ruleTester.run('naming-convention', rule, { line: 3, messageId: 'doesNotMatchFormat', data: { - // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum - type: 'Property', + type: 'Object Literal Property', name: 'Property Name', formats: 'strictCamelCase', }, @@ -1331,5 +1584,245 @@ ruleTester.run('naming-convention', rule, { }, ], }, + { + code: ` + export const PascalCaseVar = 1; + export enum PascalCaseEnum {} + export class PascalCaseClass {} + export function PascalCaseFunction() {} + export interface PascalCaseInterface {} + export type PascalCaseType = {}; + `, + options: [ + { + selector: 'default', + format: ['snake_case'], + }, + { + selector: 'variable', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'function', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'class', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'interface', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'typeAlias', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'enum', + format: ['camelCase'], + modifiers: ['exported'], + }, + ], + errors: Array(6).fill({ messageId: 'doesNotMatchFormat' }), + }, + { + code: ` + const PascalCaseVar = 1; + enum PascalCaseEnum {} + class PascalCaseClass {} + function PascalCaseFunction() {} + interface PascalCaseInterface {} + type PascalCaseType = {}; + + export { + PascalCaseVar, + PascalCaseEnum, + PascalCaseClass, + PascalCaseFunction, + PascalCaseInterface, + PascalCaseType, + }; + `, + options: [ + { selector: 'default', format: ['snake_case'] }, + { + selector: 'variable', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'function', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'class', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'interface', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'typeAlias', + format: ['camelCase'], + modifiers: ['exported'], + }, + { + selector: 'enum', + format: ['camelCase'], + modifiers: ['exported'], + }, + ], + errors: Array(6).fill({ messageId: 'doesNotMatchFormat' }), + }, + { + code: ` + const PascalCaseVar = 1; + function PascalCaseFunction() {} + declare function PascalCaseDeclaredFunction() { + }; + `, + options: [ + { selector: 'default', format: ['snake_case'] }, + { + selector: 'variable', + format: ['camelCase'], + modifiers: ['global'], + }, + { + selector: 'function', + format: ['camelCase'], + modifiers: ['global'], + }, + ], + errors: Array(3).fill({ messageId: 'doesNotMatchFormat' }), + }, + { + code: ` + const { some_name1 } = {}; + const { ignore: IgnoredDueToModifiers1 } = {}; + const { some_name2 = 2 } = {}; + + const IgnoredDueToModifiers2 = 1; + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'variable', + format: ['UPPER_CASE'], + modifiers: ['destructured'], + }, + ], + errors: Array(2).fill({ messageId: 'doesNotMatchFormat' }), + }, + { + code: ` + class Ignored { + private static abstract readonly some_name = 1; + IgnoredDueToModifiers = 1; + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'classProperty', + format: ['UPPER_CASE'], + modifiers: ['static', 'readonly'], + }, + ], + errors: [{ messageId: 'doesNotMatchFormat' }], + }, + { + code: ` + class Ignored { + constructor(private readonly some_name, IgnoredDueToModifiers) {} + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'parameterProperty', + format: ['UPPER_CASE'], + modifiers: ['readonly'], + }, + ], + errors: [{ messageId: 'doesNotMatchFormat' }], + }, + { + code: ` + class Ignored { + private static abstract some_name() {} + IgnoredDueToModifiers() {} + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'classMethod', + format: ['UPPER_CASE'], + modifiers: ['abstract', 'static'], + }, + ], + errors: [{ messageId: 'doesNotMatchFormat' }], + }, + { + code: ` + class Ignored { + private static get some_name() {} + get IgnoredDueToModifiers() {} + } + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'accessor', + format: ['UPPER_CASE'], + modifiers: ['private', 'static'], + }, + ], + errors: [{ messageId: 'doesNotMatchFormat' }], + }, + { + code: ` + abstract class some_name {} + class IgnoredDueToModifier {} + `, + options: [ + { + selector: 'default', + format: ['PascalCase'], + }, + { + selector: 'class', + format: ['UPPER_CASE'], + modifiers: ['abstract'], + }, + ], + errors: [{ messageId: 'doesNotMatchFormat' }], + }, ], }); diff --git a/packages/scope-manager/src/referencer/PatternVisitor.ts b/packages/scope-manager/src/referencer/PatternVisitor.ts index eaca842cbd59..a969851ea87d 100644 --- a/packages/scope-manager/src/referencer/PatternVisitor.ts +++ b/packages/scope-manager/src/referencer/PatternVisitor.ts @@ -52,6 +52,15 @@ class PatternVisitor extends VisitorBase { this.#callback = callback; } + private visitAssignment( + node: TSESTree.AssignmentExpression | TSESTree.AssignmentPattern, + ): void { + this.#assignments.push(node); + this.visit(node.left); + this.rightHandNodes.push(node.right); + this.#assignments.pop(); + } + protected ArrayExpression(node: TSESTree.ArrayExpression): void { node.elements.forEach(this.visit, this); } @@ -62,19 +71,9 @@ class PatternVisitor extends VisitorBase { } } - protected AssignmentExpression(node: TSESTree.AssignmentExpression): void { - this.#assignments.push(node); - this.visit(node.left); - this.rightHandNodes.push(node.right); - this.#assignments.pop(); - } + protected AssignmentExpression = this.visitAssignment; - protected AssignmentPattern(pattern: TSESTree.AssignmentPattern): void { - this.#assignments.push(pattern); - this.visit(pattern.left); - this.rightHandNodes.push(pattern.right); - this.#assignments.pop(); - } + protected AssignmentPattern = this.visitAssignment; protected CallExpression(node: TSESTree.CallExpression): void { // arguments are right hand nodes. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy