diff --git a/.README/README.md b/.README/README.md index 056df1a..be062c4 100644 --- a/.README/README.md +++ b/.README/README.md @@ -54,6 +54,10 @@ npm install eslint babel-eslint eslint-plugin-flowtype --save-dev 2, "never" ], + "flowtype/interface-id-match": [ + 2, + "^([A-Z][a-z0-9]+)+Type$" + ], "flowtype/no-mixed": 2, "flowtype/no-primitive-constructor-types": 2, "flowtype/no-types-missing-file-annotation": 2, @@ -162,6 +166,7 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d {"gitdown": "include", "file": "./rules/delimiter-dangle.md"} {"gitdown": "include", "file": "./rules/enforce-line-break.md"} {"gitdown": "include", "file": "./rules/generic-spacing.md"} +{"gitdown": "include", "file": "./rules/interface-id-match.md"} {"gitdown": "include", "file": "./rules/newline-after-flow-annotation.md"} {"gitdown": "include", "file": "./rules/no-dupe-keys.md"} {"gitdown": "include", "file": "./rules/no-existential-type.md"} diff --git a/.README/rules/interface-id-match.md b/.README/rules/interface-id-match.md new file mode 100644 index 0000000..39fdc90 --- /dev/null +++ b/.README/rules/interface-id-match.md @@ -0,0 +1,22 @@ +### `interface-id-match` + +Enforces a consistent naming pattern for interfaces. + +#### Options + +This rule requires a text RegExp: + +```js +{ + "rules": { + "flowtype/interface-id-match": [ + 2, + "^([A-Z][a-z0-9]*)+Type$" + ] + } +} +``` + +`'^([A-Z][a-z0-9]*)+Type$$'` is the default pattern. + + diff --git a/README.md b/README.md index 6e6b24a..468beba 100644 --- a/README.md +++ b/README.md @@ -1870,7 +1870,7 @@ import Foo from './foo'; // Message: Expected newline after flow annotation // Options: ["always-windows"] -// @flow +// @flow import Foo from './foo'; // Message: Expected newline after flow annotation @@ -1890,8 +1890,8 @@ The following patterns are not considered problems: import Foo from './foo'; // Options: ["always-windows"] -// @flow - +// @flow + import Foo from './foo'; // Options: ["never"] @@ -3028,7 +3028,7 @@ The rule has two options: } ``` -* `allowNull` – allows compound types where one of the members is a `null`, e.g. `string | null`. +* `allowNull` – allows compound types where one of the members is a `null`, e.g. `string | null`. The following patterns are considered problems: @@ -5392,7 +5392,7 @@ The following patterns are not considered problems: { a: string, b: number }) => {} // Options: ["always",{"allowLineBreak":true}] -(foo: +(foo: { a: string, b: number }) => {} // Options: ["never"] @@ -6719,4 +6719,3 @@ function x(foo: Type = bar()) {} ``` - diff --git a/src/index.js b/src/index.js index 04b2973..66bc783 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,7 @@ import defineFlowType from './rules/defineFlowType'; import delimiterDangle from './rules/delimiterDangle'; import enforceLineBreak from './rules/enforceLineBreak'; import genericSpacing from './rules/genericSpacing'; +import interfaceIdMatch from './rules/interfaceIdMatch'; import newlineAfterFlowAnnotation from './rules/newlineAfterFlowAnnotation'; import noDupeKeys from './rules/noDupeKeys'; import noExistentialType from './rules/noExistentialType'; @@ -56,6 +57,7 @@ const rules = { 'delimiter-dangle': delimiterDangle, 'enforce-line-break': enforceLineBreak, 'generic-spacing': genericSpacing, + 'interface-id-match': interfaceIdMatch, 'newline-after-flow-annotation': newlineAfterFlowAnnotation, 'no-dupe-keys': noDupeKeys, 'no-existential-type': noExistentialType, @@ -113,6 +115,7 @@ export default { 'define-flow-type': 0, 'delimiter-dangle': 0, 'generic-spacing': 0, + 'interface-id-match': 0, 'newline-after-flow-annotation': 0, 'no-dupe-keys': 0, 'no-flow-fix-me-comments': 0, diff --git a/src/rules/interfaceIdMatch.js b/src/rules/interfaceIdMatch.js new file mode 100644 index 0000000..47b689f --- /dev/null +++ b/src/rules/interfaceIdMatch.js @@ -0,0 +1,29 @@ +const schema = [ + { + type: 'string', + }, +]; + +const create = (context) => { + const pattern = new RegExp(context.options[0] || '^([A-Z][a-z0-9]*)+Type$'); + + const checkInterface = (interfaceDeclarationNode) => { + const interfaceIdentifierName = interfaceDeclarationNode.id.name; + + if (!pattern.test(interfaceIdentifierName)) { + context.report(interfaceDeclarationNode, 'Interface identifier \'{{name}}\' does not match pattern \'{{pattern}}\'.', { + name: interfaceIdentifierName, + pattern: pattern.toString(), + }); + } + }; + + return { + InterfaceDeclaration: checkInterface, + }; +}; + +export default { + create, + schema, +}; diff --git a/tests/rules/assertions/interfaceIdMatch.js b/tests/rules/assertions/interfaceIdMatch.js new file mode 100644 index 0000000..f160111 --- /dev/null +++ b/tests/rules/assertions/interfaceIdMatch.js @@ -0,0 +1,63 @@ +export default { + invalid: [ + { + code: 'interface foo{};', + errors: [ + { + message: 'Interface identifier \'foo\' does not match pattern \'/^([A-Z][a-z0-9]*)+Type$/\'.', + }, + ], + }, + { + code: 'interface FooType{};', + errors: [ + { + message: 'Interface identifier \'FooType\' does not match pattern \'/^foo$/\'.', + }, + ], + options: [ + '^foo$', + ], + }, + ], + misconfigured: [ + { + errors: [ + { + data: 7, + dataPath: '[0]', + keyword: 'type', + message: 'should be string', + params: { + type: 'string', + }, + parentSchema: { + type: 'string', + }, + schema: 'string', + schemaPath: '#/items/0/type', + }, + ], + options: [7], + }, + ], + valid: [ + { + code: 'interface FooType {};', + }, + { + code: 'interface foo {};', + options: [ + '^foo$', + ], + }, + { + code: 'interface foo {};', + settings: { + flowtype: { + onlyFilesWithFlowAnnotation: true, + }, + }, + }, + ], +}; diff --git a/tests/rules/index.js b/tests/rules/index.js index 7fddff4..619b6a0 100644 --- a/tests/rules/index.js +++ b/tests/rules/index.js @@ -19,6 +19,7 @@ const reportingRules = [ 'delimiter-dangle', 'enforce-line-break', 'generic-spacing', + 'interface-id-match', 'newline-after-flow-annotation', 'no-dupe-keys', 'no-existential-type',