diff --git a/packages/eslint-plugin/docs/rules/interface-name-prefix.md b/packages/eslint-plugin/docs/rules/interface-name-prefix.md index 309dcbe4f24..802a72f7bac 100644 --- a/packages/eslint-plugin/docs/rules/interface-name-prefix.md +++ b/packages/eslint-plugin/docs/rules/interface-name-prefix.md @@ -19,6 +19,8 @@ This rule has an object option: - `{ "prefixWithI": "always" }`: requires all interfaces be prefixed with `"I"` (but does not allow `"_I"`) - `{ "prefixWithI": "always", "allowUnderscorePrefix": true }`: requires all interfaces be prefixed with either `"I"` or `"_I"` +- `{ "prefixWithI": "always", "allowUnderscorePrefix": true, replacePrefixI: "A" }`: requires all interfaces be prefixed with + either `"A"` or `"_A"` For backwards compatibility, this rule supports a string option instead: @@ -123,6 +125,34 @@ interface _IAnimal { } ``` +### always and replace default prefix + +**Configuration:** `{ "prefixWithI": "always", "replacePrefixI": "A" }` + +The following patterns are considered warnings: + +```ts +interface IAnimal { + name: string; +} + +interface IPeople { + name: string; +} +``` + +The following patterns are not warnings: + +```ts +interface AAnimal { + name: string; +} + +interface APeople { + name: string; +} +``` + ## When Not To Use It If you do not want to enforce interface name prefixing. diff --git a/packages/eslint-plugin/src/rules/interface-name-prefix.ts b/packages/eslint-plugin/src/rules/interface-name-prefix.ts index 13284fc2aa3..60bd76ab603 100644 --- a/packages/eslint-plugin/src/rules/interface-name-prefix.ts +++ b/packages/eslint-plugin/src/rules/interface-name-prefix.ts @@ -7,6 +7,7 @@ type ParsedOptions = | { prefixWithI: 'always'; allowUnderscorePrefix: boolean; + replacePrefixI: string; }; type Options = [ @@ -18,6 +19,7 @@ type Options = [ | { prefixWithI: 'always'; allowUnderscorePrefix?: boolean; + replacePrefixI?: string; }, ]; type MessageIds = 'noPrefix' | 'alwaysPrefix'; @@ -26,13 +28,19 @@ type MessageIds = 'noPrefix' | 'alwaysPrefix'; * Parses a given value as options. */ export function parseOptions([options]: Options): ParsedOptions { + const replacePrefixI = 'I'; if (options === 'always') { - return { prefixWithI: 'always', allowUnderscorePrefix: false }; + return { + prefixWithI: 'always', + allowUnderscorePrefix: false, + replacePrefixI, + }; } if (options !== 'never' && options.prefixWithI === 'always') { return { prefixWithI: 'always', allowUnderscorePrefix: !!options.allowUnderscorePrefix, + replacePrefixI: options.replacePrefixI || replacePrefixI, }; } return { prefixWithI: 'never' }; @@ -52,7 +60,7 @@ export default util.createRule({ }, messages: { noPrefix: 'Interface name must not be prefixed with "I".', - alwaysPrefix: 'Interface name must be prefixed with "I".', + alwaysPrefix: 'Interface name must be prefixed with "{{prefix}}".', }, schema: [ { @@ -85,6 +93,9 @@ export default util.createRule({ allowUnderscorePrefix: { type: 'boolean', }, + replacePrefixI: { + type: 'string', + }, }, required: ['prefixWithI'], // required to select this "oneOf" alternative additionalProperties: false, @@ -98,27 +109,29 @@ export default util.createRule({ const parsedOptions = parseOptions([options]); /** - * Checks if a string is prefixed with "I". + * Checks if a string is prefixed with "@param prefix". * @param name The string to check + * @param prefix Check prefix */ - function isPrefixedWithI(name: string): boolean { - if (typeof name !== 'string') { + function isPrefixedWithI(name: string, prefix = 'I'): boolean { + if (typeof name !== 'string' || typeof prefix !== 'string') { return false; } - return /^I[A-Z]/.test(name); + return new RegExp(`^${prefix}[A-Z]`).test(name); } /** - * Checks if a string is prefixed with "I" or "_I". + * Checks if a string is prefixed with "@param prefix" or "_@param prefix". * @param name The string to check + * @param prefix Check prefix */ - function isPrefixedWithIOrUnderscoreI(name: string): boolean { - if (typeof name !== 'string') { + function isPrefixedWithIOrUnderscoreI(name: string, prefix = 'I'): boolean { + if (typeof name !== 'string' || typeof prefix !== 'string') { return false; } - return /^_?I[A-Z]/.test(name); + return new RegExp(`^_?${prefix}[A-Z]`).test(name); } return { @@ -128,21 +141,35 @@ export default util.createRule({ context.report({ node: node.id, messageId: 'noPrefix', + data: { + prefix: 'I', + }, }); } } else { if (parsedOptions.allowUnderscorePrefix) { - if (!isPrefixedWithIOrUnderscoreI(node.id.name)) { + if ( + !isPrefixedWithIOrUnderscoreI( + node.id.name, + parsedOptions.replacePrefixI, + ) + ) { context.report({ node: node.id, messageId: 'alwaysPrefix', + data: { + prefix: parsedOptions.replacePrefixI || 'I', + }, }); } } else { - if (!isPrefixedWithI(node.id.name)) { + if (!isPrefixedWithI(node.id.name, parsedOptions.replacePrefixI)) { context.report({ node: node.id, messageId: 'alwaysPrefix', + data: { + prefix: parsedOptions.replacePrefixI || 'I', + }, }); } } diff --git a/packages/eslint-plugin/tests/rules/interface-name-prefix.test.ts b/packages/eslint-plugin/tests/rules/interface-name-prefix.test.ts index 337a96a368f..c75504b6b67 100644 --- a/packages/eslint-plugin/tests/rules/interface-name-prefix.test.ts +++ b/packages/eslint-plugin/tests/rules/interface-name-prefix.test.ts @@ -8,6 +8,7 @@ describe('interface-name-prefix', () => { assert.deepEqual(parseOptions(['always']), { prefixWithI: 'always', allowUnderscorePrefix: false, + replacePrefixI: 'I', }); assert.deepEqual(parseOptions([{}]), { prefixWithI: 'never' }); assert.deepEqual(parseOptions([{ prefixWithI: 'never' }]), { @@ -16,10 +17,23 @@ describe('interface-name-prefix', () => { assert.deepEqual(parseOptions([{ prefixWithI: 'always' }]), { prefixWithI: 'always', allowUnderscorePrefix: false, + replacePrefixI: 'I', }); assert.deepEqual( parseOptions([{ prefixWithI: 'always', allowUnderscorePrefix: true }]), - { prefixWithI: 'always', allowUnderscorePrefix: true }, + { + prefixWithI: 'always', + allowUnderscorePrefix: true, + replacePrefixI: 'I', + }, + ); + assert.deepEqual( + parseOptions([{ prefixWithI: 'always', replacePrefixI: 'T' }]), + { + prefixWithI: 'always', + allowUnderscorePrefix: false, + replacePrefixI: 'T', + }, ); }); }); @@ -83,6 +97,28 @@ interface I18n { `, options: ['never'], }, + { + code: ` +interface AAnimal { + name: string; +} + `, + options: [{ prefixWithI: 'always', replacePrefixI: 'A' }], + }, + { + code: ` +interface _AAnimal { + name: string; +} + `, + options: [ + { + prefixWithI: 'always', + replacePrefixI: 'A', + allowUnderscorePrefix: true, + }, + ], + }, ], invalid: [ { @@ -189,5 +225,26 @@ interface _IAnimal { }, ], }, + { + code: ` +interface IAnimal { + name: string; +} + `, + options: [ + { + prefixWithI: 'always', + allowUnderscorePrefix: true, + replacePrefixI: 'A', + }, + ], + errors: [ + { + messageId: 'alwaysPrefix', + line: 2, + column: 11, + }, + ], + }, ], });