diff --git a/.README/rules/require-compound-type-alias.md b/.README/rules/require-compound-type-alias.md new file mode 100644 index 00000000..3d688d85 --- /dev/null +++ b/.README/rules/require-compound-type-alias.md @@ -0,0 +1,14 @@ +### `require-compound-type-alias` + +Requires to make a type alias for all [union](https://flow.org/en/docs/types/unions/) and [intersection](https://flow.org/en/docs/types/intersections/) types. If these are used in "raw" forms it might be tempting to just copy&paste them around the code. However, this brings sort of a source code pollution and unnecessary changes on several parts when these compound types need to be changed. + +#### Options + +The rule has a string option: + +* `"never"` +* `"always"` + +The default value is `"always"`. + + diff --git a/src/index.js b/src/index.js index 673833f0..3b3d99ed 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,7 @@ import noTypesMissingFileAnnotation from './rules/noTypesMissingFileAnnotation'; import noUnusedExpressions from './rules/noUnusedExpressions'; import noWeakTypes from './rules/noWeakTypes'; import objectTypeDelimiter from './rules/objectTypeDelimiter'; +import requireCompoundTypeAlias from './rules/requireCompoundTypeAlias'; import requireExactType from './rules/requireExactType'; import requireParameterType from './rules/requireParameterType'; import requireReturnType from './rules/requireReturnType'; @@ -51,6 +52,7 @@ const rules = { 'no-unused-expressions': noUnusedExpressions, 'no-weak-types': noWeakTypes, 'object-type-delimiter': objectTypeDelimiter, + 'require-compound-type-alias': requireCompoundTypeAlias, 'require-exact-type': requireExactType, 'require-parameter-type': requireParameterType, 'require-return-type': requireReturnType, @@ -94,6 +96,7 @@ export default { 'no-mutable-array': 0, 'no-weak-types': 0, 'object-type-delimiter': 0, + 'require-compound-type-alias': 0, 'require-exact-type': 0, 'require-parameter-type': 0, 'require-return-type': 0, diff --git a/src/rules/requireCompoundTypeAlias.js b/src/rules/requireCompoundTypeAlias.js new file mode 100644 index 00000000..54598c5b --- /dev/null +++ b/src/rules/requireCompoundTypeAlias.js @@ -0,0 +1,38 @@ +const schema = [ + { + enum: ['always', 'never'], + type: 'string' + } +]; + +const create = (context) => { + const always = (context.options[0] || 'always') === 'always'; + + if (always) { + return { + IntersectionTypeAnnotation (node) { + if (node.parent.type !== 'TypeAlias') { + context.report({ + message: 'All intersection types must be declared with named type alias.', + node + }); + } + }, + UnionTypeAnnotation (node) { + if (node.parent.type !== 'TypeAlias') { + context.report({ + message: 'All union types must be declared with named type alias.', + node + }); + } + } + }; + } else { + return {}; + } +}; + +export default { + create, + schema +}; diff --git a/tests/rules/assertions/requireCompoundTypeAlias.js b/tests/rules/assertions/requireCompoundTypeAlias.js new file mode 100644 index 00000000..e8c57b84 --- /dev/null +++ b/tests/rules/assertions/requireCompoundTypeAlias.js @@ -0,0 +1,92 @@ +export default { + invalid: [ + { + code: 'function foo(bar: "A" | "B") {}', + errors: [{message: 'All union types must be declared with named type alias.'}] + }, + { + code: 'const foo: "A" | "B" = "A";', + errors: [{message: 'All union types must be declared with named type alias.'}] + }, + { + code: 'type Foo = { bar: "A" | "B" };', + errors: [{message: 'All union types must be declared with named type alias.'}] + }, + { + code: 'function foo(bar: { n: number } | { s: string }) {}', + errors: [{message: 'All union types must be declared with named type alias.'}] + }, + { + code: 'function foo(bar: { n: number } & { s: string }) {}', + errors: [{message: 'All intersection types must be declared with named type alias.'}] + }, + { + code: 'const foo: { n: number } & { s: string } = { n: 0, s: "" };', + errors: [{message: 'All intersection types must be declared with named type alias.'}] + }, + { + code: 'type Foo = { bar: { n: number } & { s: string } };', + errors: [{message: 'All intersection types must be declared with named type alias.'}] + }, + { + code: 'function foo(bar: { n: number } & { s: string }) {}', + errors: [{message: 'All intersection types must be declared with named type alias.'}] + } + ], + misconfigured: [ + { + errors: [ + { + data: 'sometimes', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['sometimes'] + } + ], + valid: [ + { + code: 'type Foo = "A" | "B";' + }, + { + code: 'type Bar = "A" | "B"; function foo(bar: Bar) {}' + }, + { + code: 'type Foo = { disjoint: "A", n: number } | { disjoint: "B", s: string };' + }, + { + code: 'type Foo = { n: number } & { s: string };' + }, + { + code: 'type Bar = { n: number } & { s: string }; function foo(bar: Bar) {}' + }, + { + code: 'function foo(bar: "A" | "B") {}', + options: ['never'] + }, + { + code: 'function foo(bar: { n: number } & { s: string }) {}', + options: ['never'] + } + ] +}; diff --git a/tests/rules/index.js b/tests/rules/index.js index 7677c5b5..27271e06 100644 --- a/tests/rules/index.js +++ b/tests/rules/index.js @@ -26,6 +26,7 @@ const reportingRules = [ 'no-unused-expressions', 'no-weak-types', 'object-type-delimiter', + 'require-compound-type-alias', 'require-exact-type', 'require-parameter-type', 'require-return-type',